var Command = function (input, prompt) {
    this.input = input;
    this.prompt = '';
    this.name = '';
    this.name_list = [];
    this.options = {};
    this.content = [];
    // parse prompt
    this.PrompParser(prompt);
    // parse input
    this.InputParser(input);
}

Command.prototype.PrompParser = function (prompt) {
    if (prompt.length > 1) {
        this.name = prompt.substring(1).trim();
        this.prompt = this.name;
    }
}

Command.prototype.displayCommand = function (terminal, commands, checkInput, cmd, contents) {
    var autocompletions = [];
    for (let key in commands) {
        if (key == cmd) {
            terminal.input.val(terminal.input.val() + cmd + " ");
            let cmdInstance = this.getCmdInstance(commands[key]);
            if (cmdInstance && cmdInstance.hasOwnProperty('subCmds')) {
                let subCmd = contents.length > 0 ? contents.shift() : '';
                this.displayCommand(terminal, cmdInstance.subCmds, checkInput, subCmd, contents);
            } else if (commands[key].hasOwnProperty('subCmds')) {
                let subCmd = contents.length > 0 ? contents.shift() : '';
                this.displayCommand(terminal, commands[key].subCmds, checkInput, subCmd, contents);
            } else if (contents.length > 0) {
                terminal.input.val(terminal.input.val() + contents.join(' '));
            }
            break;
        } else if (key.startsWith(cmd)) {
            autocompletions.push(key);
        }
    }
    if (autocompletions.length == 1) {
        terminal.input.val(terminal.input.val() + autocompletions[0] + " ");
    } else if (autocompletions.length > 1) {
        terminal.displayOutput(autocompletions.join(', '));
        terminal.input.val(checkInput + this.GetCommonStr(cmd, autocompletions));
    }
}

Command.prototype.GetCommonStr = function (cmd, commands) {
    var str = '';
    var indexof = cmd.length;
    var start = indexof == 0 ? ' ' : '';
    while (true) {
        for (let i in commands) {
            let char = commands[i].charAt(indexof);
            if (char == '' || (str != '' && str != char)) {
                return start;
            }
            str = char;
        }
        start += str;
        str = '';
        indexof++;
    }

}

Command.prototype.GetInputArgs = function () {
    return {
        input: this.input,
        cmd: this.name,
        options: this.options,
        content: this.content,
        prompt: this.prompt
    }
}

Command.prototype.TabCompleteSystem = function (terminal) {
    var caretPos = terminal.input[0].selectionStart;
    var checkInput = $.trim(this.input.substr(0, caretPos));
    var nextInput = this.input.substr(caretPos);
    var checkContents = checkInput.split(' ');
    var cmd = checkContents.length > 0 ? checkContents.shift() : '';
    terminal.input.val('');
    this.displayCommand(terminal, terminal.all_commands, checkInput, cmd, checkContents);
    var completeInput = terminal.input.val();
    if (nextInput) {
        terminal.input.val(completeInput + nextInput);
        terminal.input[0].setSelectionRange(completeInput.length, completeInput.length);
    }
}

Command.prototype.TabComplete = function (terminal, from_remote) {
    var self = this;
    var cmdInstance = this.getCmdInstance(terminal.all_commands[this.name]);
    if (cmdInstance && typeof cmdInstance.TabComplete == 'function') {
        // 判断命令是当前页面执行还是远端执行
        if (!from_remote && cmdInstance.hasOwnProperty('config') && cmdInstance.config.hasOwnProperty('site')) {
            api_send_message({
                type: 'exec-remote-tab',
                config: cmdInstance.config,
                item: {
                    'showType': 'background',
                    'input': self.input,
                },
                callback: function (result) {
                    var message = result && result.hasOwnProperty('data') ? result.data : [];
                    terminal.displayOutput(terminal.formateOutput(message, from_remote));
                    terminal.input.val(self.input);
                }
            });
        } else {
            var result = cmdInstance.TabComplete(this.GetInputArgs());
            terminal.input.val(self.input);
            return result;
        }
        return [];
    } else {
        this.TabCompleteSystem(terminal);
        return 'TabCompleteSystem';
    }
}

Command.prototype.getCmdInstance = function (cmdDefine) {
    let cmdInstance = null;
    try {
        if (typeof cmdDefine.index_func == 'function') {
            cmdInstance = (new cmdDefine.index_func());
        } else if (typeof cmdDefine.index_func == 'string' && cmdDefine.index_func.trim()) {
            cmdInstance = eval('new ' + cmdDefine.index_func + '()');
        } else {
            cmdInstance = cmdDefine.index_func;
        }
    } catch (e) { }

    return cmdInstance;
}

Command.InitExec = function (terminal, cmd, cmdDefine) {
    try {
        if (typeof cmdDefine.init_func == 'function') {
            cmdDefine.init_func(terminal);
        } else if (typeof cmdDefine.init_func == 'string' && cmdDefine.init_func.trim()) {
            eval(cmdDefine.init_func + '(terminal)');
        } else {
            // console.log("Unknown " + cmd + " init function.")
        }
    } catch (e) {
        console.log(cmd + " init error [" + e + "]");
    }
}

Command.prototype.Exec = function (terminal, from_remote, callback) {
    var self = this;
    var shown_input = this.input;
    if (terminal.input.attr('type') === 'password') {
        shown_input = new Array(shown_input.length + 1).join("•");
    }

    terminal.displayInput(shown_input);
    terminal.validator(self.name);
    self.name_list = [self.name];

    let cmdInstance = self.getCmdInstance(terminal.all_commands[self.name]);
    // change simple options to normal options
    this.options = self.TransferSimpleOptions(cmdInstance);
    var exec = function (instance, exec_type) {
        // 执行Exec之前先执行初始化
        if (typeof instance._init_ == 'function') {
            let res = instance._init_(self.GetInputArgs(), terminal);
            if (res === false) {
                return null
            }
        }
        // exec this cmd
        if (instance.hasOwnProperty('subCmds')) {
            if (self.content.length > 0) {
                if (instance.subCmds.hasOwnProperty(self.content[0])) {
                    let subCmd = self.content.shift();
                    self.name_list.push(subCmd);
                    return exec(instance.subCmds[subCmd], exec_type);
                }
            }
        }

        if (self.options.hasOwnProperty('background')) {
            terminal.toggleCmdWin('hide');
        }

        if (self.options.hasOwnProperty('help')) {
            var help = new helpCmd();
            return help.printCmdDetail('', instance, $.trim(self.name_list.join(' ')));
        }

        // check the required option
        let throwStr = '';
        for (let opt in instance.options) {
            if (getPropertyVal(instance.options[opt], "required", null) !== null) {
                throwStr += self.RequiredParamsHelp(instance, opt)
            }
        }
        if (throwStr) {
            throw '<ul class="help-doc">'+throwStr+'<li><a class="command-exec" data-command="'+self.input+' --help">--help</a> to show more details.</li></ul>';
        }

        if (exec_type == 'Exec') {
            if (typeof instance.Exec == 'function') {
                let result = instance.Exec(self.GetInputArgs(), terminal);
                terminal.showInputType();
                return result;
            } else {
                if (instance.hasOwnProperty('subCmds')) {
                    let namelist = self.name_list.join(' ');
                    for (let k in instance.subCmds) {
                        terminal.displayOutput('<code class="command-exec">' + namelist + ' ' + k + '</code> ' + getPropertyVal(instance.subCmds[k], 'desc', ''));
                    }
                } else {
                    terminal.displayOutput('Command without handle function.');
                }
            }
            return null;
        } else if (exec_type == 'config') {
            return instance.hasOwnProperty('config') ? instance.config : {};
        }
        return null;
    }

    // 判断命令是当前页面执行还是远端执行
    if (!from_remote && cmdInstance.hasOwnProperty('site') && location.href.indexOf(cmdInstance.site) == -1) {
        var config = exec(cmdInstance, 'config');
        config = $.extend(cmdInstance.config, config);
        api_send_message({
            type: 'exec-remote-command',
            site: cmdInstance.site,
            config: config,
            item: {
                'showType': 'background',
                'cmds': [self.input],
            },
            callback: function (result) {
                let cmd_datas = result && result.hasOwnProperty('data') ? result.data : [];
                if (cmd_datas.length > 0) {
                    for (let i in cmd_datas) {
                        if (typeof callback == 'function') callback(cmd_datas[i]);
                    }
                } else {
                    if (typeof callback == 'function') callback([]);
                }
            }
        });
    } else {
        var res = exec(cmdInstance, 'Exec');
        if (typeof callback == 'function') callback(res);
    }

}

Command.prototype.RequiredParamsHelp = function(cmdConf, opt, exit) {
    let throwStr = '';
    if (!this.options.hasOwnProperty(opt)) {
        throwStr += '<li><span class="red_highlight"><code class="command-exec" data-command="'+this.input+' --'+opt+' ">--' + opt + '</code> required. \
</span><code>--'+opt+' &lt;'+opt+'&gt;</code>'
        if (typeof cmdConf.options[opt]['simple'] != 'undefined') {
            throwStr += ' or <code>-'+cmdConf.options[opt]['simple']+' &lt;'+opt+'&gt;</code>';
        }
        throwStr += '</li>'
    }
    if (exit) {
        throw '<ul class="help-doc">'+throwStr+'<li><a class="command-exec" data-command="'+this.input+' --help">--help</a> to show more details.</li></ul>';
    }
    return throwStr;

}

Command.prototype.InputParser = function (input_str) {
    let string_arr = [];
    let str = "";
    let quote = "";

    for (let i = 0; i < input_str.length; i++) {
        let chr = input_str.charAt(i);
        if (chr == " ") {
            // 如果不是引号中间出现的那么就进行分割
            if (!quote) {
                str && string_arr.push(str);
                str = "";
            } else {
                str += chr;
            }
        } else if (chr == '"' || chr == "'" || chr == "`") {
            // 如果是开头，则开始对字符串进行包裹
            if (!quote && str == "") {
                quote = chr;
            } else if (quote && quote == chr && input_str.charCodeAt(i - 1) != 92) {  // 如果是结尾
                str && string_arr.push(str);
                quote = "";
                str = "";
            } else {
                str += chr;
            }
        } else {
            str += chr;
        }
    }
    str && string_arr.push(str);

    // console.log(string_arr);
    let args = Minimist(string_arr);
    for (let opt in args) {
        if (opt != '_') {
            this.options[opt] = args[opt];
        } else {
            if (args['_'].length > 0) {
                // confirm the prompt has be setted
                if (args['_'][0].startsWith('$')) {
                    this.name = args['_'][0].substring(1);
                    args['_'].splice(0, 1);
                } else if (!this.name) {
                    this.name = args['_'][0];
                    args['_'].splice(0, 1);
                }

                this.content = args['_'];
            }
        }
    }
}

Command.prototype.TransferSimpleOptions = function (cmdInstance, index) {
    index = typeof index == 'number' ? index : 0;
    var newOptions = {};
    if (this.content.length > index && cmdInstance.hasOwnProperty('subCmds') && cmdInstance.subCmds.hasOwnProperty(this.content[index])) {
        newOptions = this.TransferSimpleOptions(cmdInstance.subCmds[this.content[index]], ++index);
    } else {
        for (let inputOption in this.options) {
            var find = false;
            if (cmdInstance.hasOwnProperty('options')) {
                for (let configOption in cmdInstance.options) {
                    if (configOption == inputOption || (cmdInstance.options[configOption].hasOwnProperty('simple') &&
                        cmdInstance.options[configOption].simple == inputOption)) {
                        if (cmdInstance.options[configOption].hasOwnProperty("default") && this.options[inputOption] === true) {
                            newOptions[configOption] = cmdInstance.options[configOption].default;
                        } else {
                            newOptions[configOption] = this.options[inputOption];
                        }
                        find = true;
                        break;
                    }
                }
            }
            if (!find) {
                newOptions[inputOption] = this.options[inputOption];
            }
        }
        // if empty options and then use default option
        if (JSON.stringify(newOptions) === '{}' && cmdInstance.hasOwnProperty("defaultOption")) {
            newOptions[cmdInstance.defaultOption] = true;
        }
    }

    // parse option value
    var configOption = cmdInstance.hasOwnProperty('options') ? cmdInstance.options : {};
    for (var opt in newOptions) {
        if (configOption.hasOwnProperty(opt) && configOption[opt].hasOwnProperty('dataType')) {
            switch (configOption[opt].dataType) {
                case 'bool':
                    if (!newOptions[opt] || ["false", "0"].indexOf(newOptions[opt]) > -1) {
                        newOptions[opt] = false;
                    } else {
                        newOptions[opt] = true;
                    }
                    break;
                case 'enum':
                    let enums = getPropertyVal(configOption[opt], 'options');
                    if (enums.indexOf(newOptions[opt]) < 0) {
                        throw 'The <code>' + opt + '</code> parameter value can only be in the list <code>[' + enums.join(', ') + ']</code>';
                    }
                    break;
                case 'text':
                    if (typeof newOptions[opt] == 'boolean') {
                        delete newOptions[opt];
                    }
                    break;
                case 'json':
                    try {
                        let jsonData = JSON.parse(newOptions[opt]);
                        newOptions[opt] = jsonData;
                    } catch (error) {
                        newOptions[opt] = {};
                    }
                    break;
            }
        }
    }

    // 加上未传递但已设定默认值的参数
    if (cmdInstance.hasOwnProperty('options')) {
        for (let configOption in cmdInstance.options) {
            if (!newOptions.hasOwnProperty(configOption) && cmdInstance.options[configOption].hasOwnProperty("default")) {
                newOptions[configOption] = cmdInstance.options[configOption].default;
            }
        }
    }

    return newOptions;
}


var searchCmd = function () {
    this.options = {
        google: {
            simple: "g",
            desc: "Use Google search engine.Useage: <code>search text -g</code> or <code>search text --google</code>",
            url: "https://www.google.com/search?q="
        },
        baidu: {
            simple: "d",
            desc: "Use Baidu search engine. Useage: <code>search text -d</code> or <code>search text --baidu</code>",
            url: "https://www.baidu.com/s?wd="
        },
        bing: {
            simple: "b",
            desc: "Use Bing search engine. Useage: <code>search text -b</code> or <code>search text --bing</code>",
            url: "https://cn.bing.com/search?q="
        }
    };
    this.desc = "Useage: <code>search text</code> use the google as the default search engine.";
    this.defaultOption = "google";
    this.Exec = (command, terminal) => {
        var nums = 0;
        for (let option in command.options) {
            if (this.options.hasOwnProperty(option)) {
                nums++;
                setTimeout(() => {
                    // console.log(command.content)
                    terminal.goToURL(encodeURI(this.options[option]['url'] + command.content.join("")));
                }, nums * 300);
            }
        }
    }
}

var translateCmd = function () {
    this.options = {
        source: {
            simple: "s",
            desc: "Source language. the default is auto."
        },
        target: {
            simple: "t",
            desc: "Target language. the default is the language set by the browser."
        },
        google: {
            simple: "g",
            desc: "Use Google translate engine. Useage: <code>translate text -g</code> or <code>translate text --google</code>",
            // url: "https://translate.google.cn/#view=home&op=translate&sl={sl}&tl={tl}&text="
            url: "https://translate.googleapis.com/translate_a/single?client=gtx&sl={sl}&tl={tl}&hl={tl}&dt=t&dt=bd&dj=1&source=icon&tk=161391.161391&q="
        },
        baidu: {
            simple: "d",
            desc: "Use Baidu translate engine. Useage: <code>translate text -d</code> or <code>translate text --baidu</code>",
            url: "https://fanyi.baidu.com/#{sl}/{tl}/"
        },
        deepl: {
            simple: "l",
            desc: "Use Deepl translate engine. Useage: <code>translate text -l</code> or <code>translate text --deepl</code>",
            url: "http://deepl.com/translator#{sl}/{tl}/"
        }
    };
    this.desc = "Useage: <code>translate text</code> use the google as the default translate engine.";
    this.Exec = (command, terminal) => {
        let source = getPropertyVal(command.options, 'source');
        let target = getPropertyVal(command.options, 'target');
        if (!source) {
            source = "auto";
        }
        if (!target) {
            target = api_locale_language();
        }
        console.log(command.options)
        if (command.options.hasOwnProperty('google') || command.options.hasOwnProperty('baidu') || command.options.hasOwnProperty('deepl')) {
            var nums = 0;
            for (let option in command.options) {
                if (this.options.hasOwnProperty(option)) {
                    nums++;
                    setTimeout(() => {
                        this.options[option]['url'] = this.options[option]['url'].replace('{sl}', source).replace('{tl}', target)
                        terminal.goToURL(encodeURI(this.options[option]['url'] + command.content.join("")));
                    }, nums * 300);
                }
            }
        } else {
            let api = 'https://translate.googleapis.com/translate_a/single?dt=t&dt=bd&dt=qc&dt=rm&dt=ex&client=gtx&hl=en&sl=' + source + '&tl=' + target + '&q=' + command.content.join(' ') + '&dj=1'
            $.ajax({
                type: 'GET',
                url: api,
                dataType: 'json',
                crossDomain: true,
                success: function (data) {
                    try {
                        let sentences = '';
                        if (data.hasOwnProperty('sentences')) {
                            for (let i in data.sentences) {
                                let trans = getPropertyVal(data.sentences[i], 'trans');
                                let orig = getPropertyVal(data.sentences[i], 'orig');
                                if (orig) sentences += '<div style="color:#ece09d;font-size:8px;">' + orig + '</div>';
                                if (trans) sentences += '<div>' + trans + '</div>';
                            }
                        }
                        let dict = sentences + '<br/>';
                        if (data.hasOwnProperty('dict')) {
                            for (let i in data.dict) {
                                let trans = data.dict[i];
                                dict += (i > 0 ? '<br/>' : '') + trans['pos'] + '<br/>';
                                let entrys = getPropertyVal(trans, 'entry', []);
                                for (let j in entrys) {
                                    dict += '<b>' + entrys[j]['word'] + '</b>  ';
                                    dict += entrys[j]['reverse_translation'].join(', ') + '<br/>';
                                }
                            }
                            if (data.hasOwnProperty('examples')) {
                                let examples = getPropertyVal(data.examples, 'example', [])
                                dict += '<br/>Examples<br/>'
                                for (let i in examples) {
                                    dict += '- <a class="command-exec" data-type="base64" data-command="' + window.btoa(unescape(encodeURIComponent('translate `' + $('<a>' + examples[i]['text'] + '</a>').text() + '`'))) + '">' + examples[i]['text'] + '</a><br/>';
                                }
                            }
                            sentences = dict;
                        }

                        terminal.displayOutput(sentences);
                    } catch (e) {
                        terminal.displayOutput("translate error: " + e);
                    }
                },
                error: function (err) {
                    console.log(err)
                }
            });
        }

        terminal.displayOutput("");
    }
}

var browserCmd = function () {
    this.desc = "Browser related commands";
    this.subCmds = {
        tabs: {
            desc: "View the tabs opened by the browser.",
            Exec: function (command, terminal) {
                var query = {};
                if (command.content.length > 0) {
                    query.title = '*' + command.content[0] + '*';
                }
                api_send_message({
                    type: "browser-tabs",
                    method: "query",
                    options: { query: query, type: 'query' },
                    callback: function (msg) {
                        let results = msg.data;
                        let showStr = "<style>div.tab{margin: 3px 0px;} div.tab a.active{color: green;} div.tab img{margin-top: -5px;vertical-align: middle;}</style>";
                        results.forEach(tab => {
                            showStr += "<div class='tab' data-type='tabs'>" + (terminal.is_extension_page ? "<img src='" + api_getIcon(tab.url) + "'>" : "") + " <a data-tab-id='" + tab.id + "' class='" + (tab.active ? 'active' : '') + "'>" + tab.title + "</a></div>";
                        });
                        terminal.displayOutput(showStr ? showStr : 'No result.');
                        terminal.options.selector.find('.cmd-output [data-type="tabs"] a[data-tab-id]').off('click').on('click', function () {
                            api_send_message({
                                type: "browser-tabs",
                                method: "update",
                                options: { tabId: $(this).data('tab-id'), type: 'update', updateProperties: { active: true } }
                            })
                        })
                    }
                });
            }
        },
        bookmark: {
            desc: "Browser bookmark <code>browser bookmark</code> or <code>browser bookmark 'query'</code>",
            Exec: function (command, terminal) {
                if (command.content.length > 0) {
                    api_send_message({
                        type: "browser-bookmarks",
                        options: { query: command.content[0], type: 'query' },
                        callback: function (msg) {
                            let results = msg.data;
                            let showStr = "<span class='green_highlight'>Search result:</span>";
                            results.forEach(element => {
                                showStr += "<div><a target='_blank' href='" + element.url + "'>" + element.title + "</a></div>";
                            });
                            terminal.displayOutput(showStr ? showStr : 'No result.')
                        }
                    });
                } else {
                    api_send_message({
                        type: "browser-bookmarks",
                        options: { numberOfItems: 15, type: 'getRecent' },
                        callback: function (msg) {
                            let results = msg.data;
                            let showStr = "<span class='green_highlight'>Recent 15: </span>";
                            results.forEach(element => {
                                showStr += "<div><a target='_blank' href='" + element.url + "'>" + element.title + "</a></div>";
                            });
                            terminal.displayOutput(showStr ? showStr : 'No result.')
                        }
                    });
                }
            }
        },
        history: {
            desc: "Browser history <code>browser history</code> or <code>browser history 'query'</code>",
            Exec: function (command, terminal) {
                api_send_message({
                    type: "browser-history",
                    options: { text: command.content.length > 0 ? command.content[0] : '', maxResults: 20 },
                    callback: function (msg) {
                        // console.log("callback msg: ", msg)
                        let results = msg.data;
                        let showStr = "<span class='green_highlight'>Search result in the past 24 hours:</span>";
                        results.forEach(element => {
                            showStr += "<div><a target='_blank' href='" + element.url + "'>" + element.title + "</a></div>";
                        });
                        terminal.displayOutput(showStr ? showStr : 'No result.')
                    }
                });
            }
        },
        notice: {
            desc: "Browser notice <code>browser notice -t basic -i https://developer.chrome.com/webstore/images/reader.png -T `It's test title` -m `main message` -M `content message`</code>",
            options: {
                type: {
                    simple: "t",
                    desc: "Which type of notification to display. type enum (basic, image, list, progress)",
                    options: ["basic", "image", "list", "progress"],
                    required: true
                },
                iconUrl: {
                    simple: "i",
                    desc: "A URL to the sender's avatar, app icon, or a thumbnail for image notifications.\
                            URLs can be a data URL, a blob URL",
                    required: true
                },
                title: {
                    simple: "T",
                    desc: "Title of the notification (e.g. sender name for email).",
                    required: true
                },
                message: {
                    simple: "m",
                    desc: "Main notification content.",
                    required: true
                },
                contextMessage: {
                    simple: "M",
                    desc: "Alternate notification content with a lower-weight font.",
                }
            },
            Exec: function (command, terminal) {
                // todo valid required params
                api_send_message({ type: "browser-notifications", options: command.options });
                terminal.displayOutput("")
            }
        }
    }
}

var decodeCmd = function () {
    this.subCmds = {
        base64: {
            desc: "base64 decode",
            Exec: function(command, terminal) {
                let words = CryptoJS.enc.Base64.parse(command.content[0]);
                terminal.displayOutput(CryptoJS.enc.Utf8.stringify(words));
            }
        },
        html: {
            desc: "html decode",
            Exec: function(command, terminal) {
                var txt = document.createElement("textarea");
                txt.innerHTML = command.content[0];
                terminal.displayOutput(txt.innerHTML);
            }
        },
        url: {
            desc: "url decode",
            Exec: function(command, terminal) {
                terminal.displayOutput(window.decodeURIComponent(command.content[0]));
            }
        },
        escape: {
            desc: "unescape encode",
            Exec: function(command, terminal) {
                terminal.displayOutput(unescape(command.content[0]));
            }
        },
        aes: {
            desc: "aes decode",
            options: {
                key: {
                    simple: "k",
                    desc: "secret key",
                    dataType: 'text',
                    required: true
                },
                iv: {
                    simple: "i",
                    desc: "secret key",
                    dataType: 'text',
                },
                mode: {
                    simple: "m",
                    desc: "encryption mode",
                    dataType: 'enum',
                    options: ["CBC", "CFB", "CTR", "OFB", "ECB"],
                    default: "CBC"
                },
                padding: {
                    simple: "p",
                    desc: "Padding",
                    dataType: 'enum',
                    options: ["Pkcs7", "Iso97971", "AnsiX923", "Iso10126", "ZeroPadding", "NoPadding"],
                    default: "Pkcs7"
                },
            },
            Exec: function(command, terminal) {
                try {
                    let param = {}
                    if (typeof command.options.iv != 'undefined') {
                        param.iv = command.options.iv
                    }
                    if (typeof command.options.mode != 'undefined') {
                        param.mode = CryptoJS.mode[command.options.mode]
                    }
                    if (typeof command.options.padding != 'undefined') {
                        param.padding = CryptoJS.pad[command.options.padding]
                    }
                    console.log(command.content[0], command.options.key, param)
                    terminal.displayOutput(CryptoJS.AES.decrypt(command.content[0], command.options.key).toString(CryptoJS.enc.Utf8));
                } catch (error) {
                    terminal.displayErrorOutput(error.message);
                }
            }
        },
    }
}
var encodeCmd = function () {
    this.subCmds = {
        base64: {
            desc: "base64 encode",
            Exec: function(command, terminal) {
                let words = CryptoJS.enc.Utf8.parse(command.content[0]);
                terminal.displayOutput(CryptoJS.enc.Base64.stringify(words));
            }
        },
        hex: {
            desc: "hex encode",
            Exec: function(command, terminal) {
                let words = CryptoJS.enc.Hex.parse(command.content[0]);
                let hex = CryptoJS.enc.Hex.stringify(words);
                terminal.displayOutput(hex);
            }
        },
        html: {
            desc: "html encode",
            Exec: function(command, terminal) {
                var temp = document.createElement("div");
                (typeof temp.textContent != undefined ) ? (temp.textContent = command.content[0]) : (temp.innerText = command.content[0]);
                console.log(temp.innerHTML)
                terminal.displayOutput(temp.innerHTML, true);
                temp = null;
            }
        },
        url: {
            desc: "url encode",
            Exec: function(command, terminal) {
                terminal.displayOutput(window.encodeURIComponent(command.content[0]));
            }
        },
        escape: {
            desc: "escape encode",
            Exec: function(command, terminal) {
                terminal.displayOutput(escape(command.content[0]));
            }
        },
        md5: {
            desc: "md5 encode",
            options: {
                iterations: {
                    simple: "i",
                    desc: "Number of iterations",
                    dataType: "bool"
                },
                toUpperCase: {
                    simple: "u",
                    desc: "to upper case",
                    dataType: "bool"
                },
                toLowerCase: {
                    simple: "l",
                    desc: "to lower case",
                    dataType: "bool"
                },
            },
            Exec: function(command, terminal) {
                let md5str = CryptoJS.MD5(command.content[0]).toString()
                if (typeof command.options.toUpperCase != 'undefined' && command.options.toUpperCase) {
                    md5str = md5str.toUpperCase()
                }
                terminal.displayOutput(md5str);
            }
        },
        sha1: {
            desc: "sha1 encode",
            options: {
                toUpperCase: {
                    simple: "u",
                    desc: "to upper case",
                    dataType: "bool"
                },
                toLowerCase: {
                    simple: "l",
                    desc: "to lower case",
                    dataType: "bool"
                },
            },
            Exec: function(command, terminal) {
                let hashStr = CryptoJS.SHA1(command.content[0]).toString()
                if (typeof command.options.toUpperCase != 'undefined' && command.options.toUpperCase) {
                    hashStr = hashStr.toUpperCase()
                }
                terminal.displayOutput(hashStr);
            }
        },
        sha2: {
            desc: "sha2 encode",
            options: {
                toUpperCase: {
                    simple: "u",
                    desc: "to upper case",
                    dataType: "bool"
                },
                toLowerCase: {
                    simple: "l",
                    desc: "to lower case",
                    dataType: "bool"
                },
                mode: {
                    simple: "m",
                    desc: "encryption mode",
                    dataType: 'enum',
                    options: ["sha-256", "sha-512"],
                    default: "sha-256"
                },
            },
            Exec: function(command, terminal) {
                let hashStr = "";
                if (command.options.mode == "sha-512") {
                    hashStr = CryptoJS.SHA512(command.content[0]).toString()
                } else {
                    hashStr = CryptoJS.SHA256(command.content[0]).toString()
                }
                if (typeof command.options.toUpperCase != 'undefined' && command.options.toUpperCase) {
                    hashStr = hashStr.toUpperCase()
                }
                terminal.displayOutput(hashStr);
            }
        },
        sha3: {
            desc: "sha3 encode",
            options: {
                toUpperCase: {
                    simple: "u",
                    desc: "to upper case",
                    dataType: "bool"
                },
                toLowerCase: {
                    simple: "l",
                    desc: "to lower case",
                    dataType: "bool"
                },
                mode: {
                    simple: "m",
                    desc: "encryption mode",
                    dataType: 'enum',
                    options: ["len-224", "len-256", "len-384", "len-512"],
                    default: "len-224"
                },
            },
            Exec: function(command, terminal) {
                let ouputLen = 224;
                if (command.options.mode) {
                    ouputLen = command.options.mode.replace("len-", "")
                }
                let hashStr = CryptoJS.SHA3(command.content[0], {outputLength: ouputLen}).toString();
                if (typeof command.options.toUpperCase != 'undefined' && command.options.toUpperCase) {
                    hashStr = hashStr.toUpperCase()
                }
                terminal.displayOutput(hashStr);
            }
        },
        ripemd160: {
            desc: "ripemd160 encode",
            options: {
                toUpperCase: {
                    simple: "u",
                    desc: "to upper case",
                    dataType: "bool"
                },
                toLowerCase: {
                    simple: "l",
                    desc: "to lower case",
                    dataType: "bool"
                },
            },
            Exec: function(command, terminal) {
                let hashStr = CryptoJS.RIPEMD160(command.content[0]).toString();
                if (typeof command.options.toUpperCase != 'undefined' && command.options.toUpperCase) {
                    hashStr = hashStr.toUpperCase()
                }
                terminal.displayOutput(hashStr);
            }
        },
        pbkdf2: {
            desc: "pbkdf2 encode",
            options: {
                salt: {
                    simple: "s",
                    desc: "salt",
                    dataType: "number",
                    required: true
                },
                keySize: {
                    simple: "k",
                    desc: "key size",
                    dataType: "number",
                    required: true
                },
                iterations: {
                    simple: "i",
                    desc: "iterations",
                    dataType: "number",
                },
                toUpperCase: {
                    simple: "u",
                    desc: "to upper case",
                    dataType: "bool"
                },
                toLowerCase: {
                    simple: "l",
                    desc: "to lower case",
                    dataType: "bool"
                },
            },
            Exec: function(command, terminal) {
                let salt = CryptoJS.lib.WordArray.random(command.options.salt);
                let parms = {
                    keySize: command.options.keySize,
                }
                if (typeof command.options.iterations != 'undefined') {
                    parms.iterations = command.options.iterations
                }
                let key512Bits1000Iterations = CryptoJS.PBKDF2(command.content[0], salt, parms).toString();
                if (typeof command.options.toUpperCase != 'undefined' && command.options.toUpperCase) {
                    key512Bits1000Iterations = key512Bits1000Iterations.toUpperCase()
                }
                terminal.displayOutput(key512Bits1000Iterations);
            }
        },
        aes: {
            desc: "AES encode",
            options: {
                key: {
                    simple: "k",
                    desc: "secret key",
                    dataType: 'text',
                    required: true
                },
                iv: {
                    simple: "i",
                    desc: "secret key",
                    dataType: 'text',
                },
                mode: {
                    simple: "m",
                    desc: "encryption mode",
                    dataType: 'enum',
                    options: ["CBC", "CFB", "CTR", "OFB", "ECB"],
                    default: "CBC"
                },
                padding: {
                    simple: "p",
                    desc: "Padding",
                    dataType: 'enum',
                    options: ["Pkcs7", "Iso97971", "AnsiX923", "Iso10126", "ZeroPadding", "NoPadding"],
                    default: "Pkcs7"
                },
            },
            Exec: function(command, terminal) {
                try {
                    let param = {}
                    if (typeof command.options.iv != 'undefined') {
                        param.iv = command.options.iv
                    }
                    if (typeof command.options.mode != 'undefined') {
                        param.mode = CryptoJS.mode[command.options.mode]
                    }
                    if (typeof command.options.padding != 'undefined') {
                        param.padding = CryptoJS.pad[command.options.padding]
                    }
                    console.log(param)
                    terminal.displayOutput(CryptoJS.AES.encrypt(command.content[0], command.options.key, param).toString());
                } catch (error) {
                    terminal.displayErrorOutput(error.message);
                }
            }
        },
        des: {
            desc: "DES encode",
            options: {
                key: {
                    simple: "k",
                    desc: "secret key",
                    dataType: 'text',
                    required: true
                },
            },
            Exec: function(command, terminal) {
                try {
                    let encrypted = CryptoJS.DES.encrypt(command.content[0], command.options.key);
                    terminal.displayOutput(encrypted.toString());
                } catch (error) {
                    terminal.displayErrorOutput("secret key must be string type. ", error.message);
                }
            }
        },
        tripleDes: {
            desc: "triple DES encode",
            options: {
                key: {
                    simple: "k",
                    desc: "secret key",
                    dataType: 'text',
                    required: true
                },
            },
            Exec: function(command, terminal) {
                try {
                    let encrypted = CryptoJS.TripleDES.encrypt(command.content[0], command.options.key);
                    terminal.displayOutput(encrypted.toString());
                } catch (error) {
                    terminal.displayErrorOutput("secret key must be string type. ", error.message);
                }
            }
        },
        rabbit: {
            desc: "Rabbit encode",
            options: {
                key: {
                    simple: "k",
                    desc: "secret key",
                    dataType: 'text',
                    required: true
                },
            },
            Exec: function(command, terminal) {
                try {
                    let encrypted = CryptoJS.Rabbit.encrypt(command.content[0], command.options.key);
                    terminal.displayOutput(encrypted.toString());
                } catch (error) {
                    terminal.displayErrorOutput("secret key must be string type. ", error.message);
                }
            }
        },
        rc4: {
            desc: "RC4 encode",
            options: {
                key: {
                    simple: "k",
                    desc: "secret key",
                    dataType: 'text',
                    required: true
                },
            },
            Exec: function(command, terminal) {
                try {
                    let encrypted = CryptoJS.RC4.encrypt(command.content[0], command.options.key);
                    terminal.displayOutput(encrypted.toString());
                } catch (error) {
                    terminal.displayErrorOutput("secret key must be string type. ", error.message);
                }
            }
        },
        rc4Drop: {
            desc: "RC4Drop encode",
            options: {
                key: {
                    simple: "k",
                    desc: "secret key",
                    dataType: 'text',
                    required: true
                },
                drop: {
                    simple: "d",
                    desc: "drop number of words.",
                    dataType: 'number',
                }
            },
            Exec: function(command, terminal) {
                let encrypted = {};
                try {
                    if (typeof command.options.drop != "undefined") {
                        encrypted = CryptoJS.RC4Drop.encrypt(command.content[0], command.options.key, {
                            drop: command.options.drop
                        });
                    } else {
                        encrypted = CryptoJS.RC4Drop.encrypt(command.content[0], command.options.key);
                    }
                    terminal.displayOutput(encrypted.toString());
                } catch (error) {
                    terminal.displayErrorOutput("secret key must be string type. ", error.message);
                }
            }
        },
    }
}

var exampleCmd = function() {
    this.subCmds = {
        lower: {
            desc: "Make a string lowercase",
            Exec: function(command, terminal) {
                terminal.displayOutput(command.content[0].toLowerCase());
            }
        },
    }
}
var githubCmd = function() {
    let self = this
    this.desc = 'github cmd'
    this.base_url = 'https://api.github.com/'
    this.conf = {
        token: '',
        user: '',
        repo: '',
        owner: '',
        path: ''
    }
    this.repourlbase = function(action, repo, user) {
        user = user ? user : this.conf.user
        return 'repos/'+user+'/'+repo+'/'+action
    }
    this._init_ = function(command, terminal) {
        $.extend(this.conf, terminal.getUserData('github'))
        if (this.isAuthInit(command)) {
            return true;
        }
        let authCmd = 'and then add it through <code>github auth</code> command. \
like this <code class="command-exec" data-command="github auth -u &lt;user&gt; -t &lt;token&gt;">\
github auth -u `web-terminal` -t `ghp_ip2Ft62Q8ESvqvk2hE0wclQSaNKNMT1W01ZQ`</code>'
        if (!this.conf.token) {
            terminal.displayOutput('Please go to <a target="_blank" href="https://github.com/settings/tokens/new">github</a> \
to get the personal access token first, '+authCmd)
            return false
        }
        if (!this.conf.user) {
            terminal.displayOutput('Please fill in the github username, not email. '+authCmd)
            return false
        }
    }

    this.isAuthInit = function(command) {
        return command.content.length > 0 && command.content[0] == 'auth'
    }

    this.setUserData = function(terminal, userData, callback) {
        let changed = false
        if (userData) {
            for (let i in userData) {
                if (typeof this.conf[i] == 'undefined' || this.conf[i] != userData[i]) {
                    changed = true
                }
                this.conf[i] = userData[i];
            }
        }
        if (changed) {
            terminal.setUserData('github', this.conf, callback)
        }
    }

    this.getUserData = function(key, defaultVal) {
        if (!this.conf.hasOwnProperty(key)) {
            return defaultVal;
        }
        return this.conf[key]
    }

    this.subCmds = {
        auth: {
            desc: 'set api auth. like this <code class="command-exec" data-command="github auth -u &lt;user&gt; -t &lt;token&gt;">github auth -u `web-terminal` -t `ghp_ip2Ft62Q8ESvqvk2hE0wclQSaNKNMT1W01ZQ`</code>',
            options: {
                user: {
                    simple: 'u',
                    desc: 'The authentication user',
                    required: true,
                    dataType: 'text'
                },
                token: {
                    simple: 't',
                    desc: 'Personal access token, go to the github website to <a target="_blank" href="https://github.com/settings/tokens/new">generate a new token</a> and copy the token to provide this parameter. it is recommended to check the write:packages,delete:packages,delete_repo permissions.',
                    required: true,
                    dataType: 'text'
                }
            },
            Exec: (command, terminal) => {
                self.setUserData(terminal, {user: command.options.user, token: command.options.token}, (msg) => {
                    terminal.displayOutput(msg.meta.data);
                })
            },
        },
        repo: {
            desc: 'github repository command',
            subCmds: {
                list: {
                    desc: 'List user\'s all repositories',
                    options: {
                        owner: {
                            simple: 'o',
                            desc: 'Which owner\'s repos. Default is the auth user. Required if no auth.',
                            dataType: 'text',
                        },
                        type: {
                            desc: 'Can be one of all, owner, public, private, member. Note: For GitHub AE, can be one of all, owner, internal, private, member.\
                            Will cause a 422 error if used in the same request as visibility or affiliation. Will cause a 422 error if used in the same request as visibility or affiliation.',
                            options: ['all', 'owner', 'public', 'private', 'member'],
                            dataType: 'enum',
                            default: 'all'
                        },
                        page: {
                            simple: 'p',
                            desc: 'Page number of the results to fetch.',
                            dataType: 'number',
                            default: 1
                        },
                        per_page: {
                            simple: 'P',
                            desc: 'Results per page (max 100)',
                            dataType: 'number',
                            default: 30
                        },
                        since: {
                            simple: 's',
                            desc: 'Only show notifications updated after the given time. This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.',
                            dataType: 'text'
                        },
                        before: {
                            simple: 'b',
                            desc: 'Only show notifications updated before the given time. This is a timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.',
                            dataType: 'text',
                        },
                        visibility: {
                            desc: 'Can be one of all, public, or private. Note: For GitHub AE, can be one of all, internal, or private.',
                            dataType: 'enum',
                            options: ['all', 'public', 'private'],
                            // default: 'all'
                        },
                        affiliation: {
                            desc: 'Comma-separated list of values. Can include:\
                            * owner: Repositories that are owned by the authenticated user.\
                            * collaborator: Repositories that the user has been added to as a collaborator.\
                            * organization_member: Repositories that the user has access to through being a member of an organization. This includes every repository on every team that the user is on.',
                            dataType: 'text',
                            // default: 'owner,collaborator,organization_member'
                        },
                        sort: {
                            desc: 'Can be one of created, updated, pushed, full_name.',
                            options: ['created', 'updated', 'pushed', 'full_name'],
                            dataType: 'enum',
                            default: 'updated'
                        },
                        direction: {
                            desc: 'Can be one of asc or desc. Default: asc when using full_name, otherwise desc',
                            options: ['asc', 'desc'],
                            dataType: 'enum',
                        },
                    },
                    Exec: (command, terminal) => {
                        let owner = self.conf.user
                        if (typeof command.options.owner != 'undefined') owner = command.options.owner
                        let data = command.options

                        if (!owner) {
                            terminal.command.RequiredParamsHelp(self.subCmds.repo.subCmds.list, 'owner', true)
                        }
        
                        let page = command.options.page;
                        if (typeof page == 'object') {
                            data.page = page[page.length-1]
                        }
                        let url = 'user/repos'
                        if (command.options.hasOwnProperty('owner')) {
                            url = 'users/'+command.options.owner+'/repos'
                        }
                        self.common('GET', url, data, terminal, function(items, status) {
                            if (status == 404) items = [];
                            let showStr = self.printRepos(items, owner, (command.options.page-1)*command.options.per_page)
                            if (items.length == command.options.per_page) {
                                let input = command.input + ' -p '+(data.page+1)
                                showStr += '<div class="content-list-1"><a class="command-exec" data-command="'+input+' "> -------more------- </a></div>';
                            }
                            terminal.displayOutput(showStr);
                        });
                    },
                },
                content: {
                    desc: 'Gets the contents of a file or directory in a repository',
                    options: {
                        owner: {
                            simple: 'o',
                            dataType: 'text',
                        },
                        repo: {
                            simple: 'r',
                            desc: 'The file of repo',
                            dataType: 'text',
                        },
                        path: {
                            simple: 'p',
                            desc: 'The file path',
                            dataType: 'text',
                        },
                        ref: {
                            simple: 'f',
                            desc: 'The name of the commit/branch/tag. Default: the repository’s default branch (usually master)',
                            dataType: 'text',
                        },
                        _view: {
                            simple: 'v',
                            desc: 'The view type of show items.',
                            dataType: 'enum',
                            options: ['rows', 'cols'],
                            default: 'rows'
                        },
                    },
                    Exec: (command, terminal) => {
                        
                        let repo = typeof command.options.repo == 'undefined' ? self.getUserData('repo') : command.options.repo;
                        let owner = typeof command.options.owner == 'undefined' ? self.getUserData('owner', self.getUserData('user')) : command.options.owner;
                        let path = typeof command.options.path == 'undefined' ? '' : command.options.path
                        
                        if (repo) {
                            self.common('GET', self.repourlbase('contents/'+path, repo, owner), command.options, terminal, function(items, status) {
                                // 文件
                                if (typeof items.type != 'undefined') {
                                    self.editFile(terminal, owner, repo, path, items);
                                    self.setUserData(terminal, {'repo': repo, 'owner': owner, 'file': path});
                                } else {
                                    if (status == 404) items = [];
                                    self.setUserData(terminal, {'repo': repo, 'owner': owner, 'path': path});
                                    terminal.displayOutput(self.printRepoContents(items, owner, repo, path, command.options._view));
                                }
                            });
                        } else {
                            terminal.command.RequiredParamsHelp(self.subCmds.repo.subCmds.content, 'repo', true)
                        }
                    },
                },
            },
        },
        create: {
            desc: 'github create command.',
            subCmds: {
                repo: {
                    desc: 'Create a repository for the authenticated user.',
                    options: {
                        name: {
                            simple: "n",
                            desc: "The name of the repository.",
                            dataType: "text",
                            required: true
                        },
                        description: {
                            simple: "d",
                            desc: "A short description of the repository.",
                            dataType: "text"
                        },
                        homepage: {
                            simple: "h",
                            desc: "A URL with more information about the repository.",
                            dataType: "text"
                        },
                        private: {
                            simple: "p",
                            desc: "Whether the repository is private.",
                            dataType: "bool",
                            default: true
                        },
                        has_issues: {
                            simple: "i",
                            desc: "Whether issues are enabled.",
                            dataType: "bool",
                            default: true
                        },
                        has_projects: {
                            simple: "P",
                            desc: "Whether projects are enabled.",
                            dataType: "bool",
                            default: true
                        },
                        has_wiki: {
                            simple: "w",
                            desc: "Whether the wiki is enabled.",
                            dataType: "bool",
                            default: true
                        },
                        auto_init: {
                            simple: "a",
                            desc: "Whether the repository is initialized with a minimal README.",
                            dataType: "bool",
                            default: false
                        },
                        team_id: {
                            desc: "The id of the team that will be granted access to this repository. This is only valid when creating a repository in an organization.",
                            dataType: "number"
                        },
                        gitignore_template: {
                            desc: "The desired language or platform to apply to the .gitignore.",
                            dataType: "text"
                        },
                        license_template: {
                            desc: "The license keyword of the open source license for this repository.",
                            dataType: "text"
                        },
                        allow_squash_merge: {
                            desc: "Whether to allow squash merges for pull requests.",
                            dataType: "bool",
                            default: true
                        },
                        allow_merge_commit: {
                            desc: "Whether to allow merge commits for pull requests.",
                            dataType: "bool",
                            default: true
                        },
                        allow_rebase_merge: {
                            desc: "Whether to allow rebase merges for pull requests.",
                            dataType: "bool",
                            default: true
                        },
                        allow_auto_merge: {
                            desc: "Whether to allow Auto-merge to be used on pull requests.",
                            dataType: "bool",
                            default: false
                        },
                        delete_branch_on_merge: {
                            desc: "Whether to delete head branches when pull requests are merged.",
                            dataType: "bool",
                            default: false
                        },
                        has_downloads: {
                            desc: "Whether downloads are enabled.",
                            dataType: "bool",
                            default: true
                        },
                        is_template: {
                            desc: "Whether this repository acts as a template that can be used to generate new repositories.",
                            dataType: "bool",
                            default: false
                        },
                    },
                    Exec: (command, terminal) => {
                        self.common('POST', 'user/repos', command.options, terminal, function(items) {
                            terminal.displayOutput("created success.");
                        });
                        
                    }
                },
                file: {
                    desc: 'Create or update file contents',
                    options: {
                        owner: {
                            simple: 'o',
                            dataType: 'text',
                        },
                        repo: {
                            simple: 'r',
                            desc: 'The file of repo',
                            dataType: 'text',
                        },
                        path: {
                            simple: 'p',
                            desc: 'The file path',
                            dataType: 'text',
                        },
                        message: {
                            simple: 'm',
                            desc: 'The commit message.',
                            dataType: 'text',
                        },
                        content: {
                            simple: 'c',
                            desc: 'The new file content.',
                            dataType: 'text',
                        },
                        encode: {
                            simple: 'e',
                            desc: 'The content has base64 encoded.',
                            dataType: 'bool'
                        },
                        sha: {
                            simple: 'h',
                            desc: 'Required if you are updating a file. The blob SHA of the file being replaced.',
                            dataType: 'text'
                        },
                        branch: {
                            simple: 'b',
                            desc: 'The branch name. Default: the repository’s default branch (usually master)',
                            dataType: 'text',
                            default: 'master'
                        },
                    },
                    Exec: (command, terminal) => {

                        let repo = typeof command.options.repo == 'undefined' ? self.getUserData('repo') : command.options.repo;
                        let owner = typeof command.options.owner == 'undefined' ? self.getUserData('owner', self.getUserData('user')) : command.options.owner;
                        let path = typeof command.options.path == 'undefined' ? '' : command.options.path

                        if (repo) {
                            let options = {content: getPropertyVal(command.options, 'content')}
                            if (getPropertyVal(command.options, 'sha')) options.sha = command.options.sha
                            if (getPropertyVal(command.options, 'encoding')) options.encoding = 'base64'
                            self.editFile(terminal, owner, repo, path, options);
                            self.setUserData(terminal, {'repo': repo, 'owner': owner, 'file': path});
                        } else {
                            terminal.command.RequiredParamsHelp(self.subCmds.create.subCmds.file, 'repo', true)
                        }
                        
                    }
                },
            },
            
        },
        delete: {
            desc: 'github delete command.',
            subCmds: {
                repo: {
                    desc: 'Deleting a repository requires admin access.',
                    options: {
                        repo: {
                            simple: 'r',
                            desc: 'The name of repo',
                            required: true,
                            dataType: 'text'
                        },
                        owner: {
                            simple: 'o',
                            desc: 'The owner of repo',
                            required: true,
                            dataType: 'text'
                        },
                    },
                    Exec: (command, terminal) => {
                        self.common('DELETE', 'repos/'+command.options.owner+'/'+command.options.repo, null, terminal, function(resp, status) {
                            if (status == 204) {
                                terminal.displayOutput("deleted success.");
                            }
                        });
                        terminal.displayOutput("You can also go to the <a target='_blank' href='https://github.com/"+command.options.owner+'/'+command.options.repo+"/settings'>github</a> website to delete.");
                    }
                },
                file: {
                    desc: 'Deletes a file in a repository.',
                    options: {
                        owner: {
                            simple: 'o',
                            dataType: 'text',
                        },
                        repo: {
                            simple: 'r',
                            desc: 'The file of repo',
                            dataType: 'text',
                        },
                        path: {
                            simple: 'p',
                            desc: 'The file path',
                            dataType: 'text',
                        },
                        message: {
                            simple: 'm',
                            desc: 'The commit message.',
                            dataType: 'text',
                        },
                        sha: {
                            simple: 'h',
                            desc: 'Required if you are delete a file. The dir will be not required.',
                            dataType: 'text',
                        },
                        branch: {
                            simple: 'b',
                            desc: 'The branch name. Default: the repository’s default branch (usually master)',
                            dataType: 'text',
                            default: 'master'
                        },
                        dir: {
                            simple: 'd',
                            desc: 'Is dir path',
                            dataType: 'bool'
                        },
                    },
                    Exec: (command, terminal) => {

                        let repo = typeof command.options.repo == 'undefined' ? self.getUserData('repo') : command.options.repo;
                        let owner = typeof command.options.owner == 'undefined' ? self.getUserData('owner', self.getUserData('user')) : command.options.owner;
                        let path = typeof command.options.path == 'undefined' ? '' : command.options.path

                        if (repo) {
                            if (getPropertyVal(command.options, 'dir')) {
                                self.common('GET', self.repourlbase('contents/'+path, repo, owner), null, terminal, function(items, status) {
                                    if (items.length > 0) {
                                        let commandList = []
                                        items.forEach((item, idx) => {
                                            let cmd = `github delete file -o ${owner} -r ${repo} -p ${item.path} -h ${item.sha}`
                                            if (item.type == 'dir') {
                                                cmd += ' -d'
                                            }
                                            commandList.push(cmd)
                                        });
                                        terminal.pipelineSeriesCommands(commandList)
                                    }
                                });
                            } else {
                                if (!getPropertyVal(command.options, 'sha')) {
                                    terminal.command.RequiredParamsHelp(self.subCmds.delete.subCmds.file, 'sha', true)
                                }
                                
                                let options = {sha: command.options.sha, message: 'Delete from web-terminal by '+owner}
                                if (getPropertyVal(command.options, 'message')) options.message = command.options.message
                                self.common('DELETE', `repos/${owner}/${repo}/contents/${path}`, options, terminal, function(resp, status) {
                                    if (status == 200) {
                                        terminal.displayOutput("deleted success.");
                                    }
                                });
                            }
                            
                            self.setUserData(terminal, {'repo': repo, 'owner': owner});
                        } else {
                            terminal.command.RequiredParamsHelp(self.subCmds.delete.subCmds.file, 'repo', true)
                        }
                        
                    }
                },
            },
        },
        ls: {
            options: {
                owner: {
                    simple: 'o',
                    dataType: 'text',
                },
                repo: {
                    simple: 'r',
                    desc: 'The file of repo',
                    dataType: 'text',
                },
                path: {
                    simple: 'p',
                    desc: 'The file path',
                    dataType: 'text',
                },
                list: {
                    simple: 'l',
                    desc: 'Show list',
                    dataType: 'bool',
                    default: false
                },
            },
            Exec: (command, terminal) => {
                let repo = typeof command.options.repo == 'undefined' ? self.getUserData('repo') : command.options.repo;
                let owner = typeof command.options.owner == 'undefined' ? self.getUserData('owner', self.getUserData('user')) : command.options.owner;
                let path = typeof command.options.path == 'undefined' ? self.getUserData('path', '') : command.options.path;
                
                if (repo) {
                    terminal.handleInput(`github repo content -o ${owner} -r ${repo}`+(command.options.list ? '' : ' -v cols')+(path ? ` -p ${path}` : ''))
                } else {
                    terminal.handleInput('github repo list')
                }
                
            }
        },
        rate: {
            Exec: (command, terminal) => {
                self.common('GET', 'rate_limit', null, terminal, function(items) {
                    for (let k in items) {
                        terminal.displayOutput(items[k])
                    }
                    
                });
            }
        }
    }

    this.editFile = function(terminal, owner, repo, path, item) {
        let source = item.content
        if (getPropertyVal(item, 'encoding') == "base64") {
            let words = CryptoJS.enc.Base64.parse(source.replace(/\s/g, ''))
            source = CryptoJS.enc.Utf8.stringify(words)
        }
        
        terminal.showEditor(source, (content) => {
            let words = CryptoJS.enc.Utf8.parse(content);
            options = {
                content: CryptoJS.enc.Base64.stringify(words),
                message: getPropertyVal(item, 'message') ? item.message : 'from webterminal opt'
            }
            if (getPropertyVal(item, 'sha')) {
                options['sha'] = item.sha;
            }
            self.common('PUT', self.repourlbase('contents', repo, owner)+'/'+path, options, terminal, function(reps) {
                terminal.displayOutput('success, <a target="_blank" href="'+reps.commit.html_url+'">'+reps.commit.html_url+'</a>');
            });
        });
    }

    this.common = (type, action, data, terminal, callback) => {
        let timestamp = new Date().getTime()
        let ajaxConfig = {
            url: this.base_url+action+'?time='+timestamp,
            method: type,
            headers: {
                Accept: "application/vnd.github.v3+json",
                Authorization: "Basic "+window.btoa(this.conf.user+':'+this.conf.token),
                "Content-Type": "application/json;charset=UTF-8"
            },
            responseType: 'json',
        }

        if (data) {
            if (type.toUpperCase() == 'GET') {
                ajaxConfig.url += ajaxConfig.url.indexOf('?') > 0 ? '&' : '?'
                for (let i in data) {
                    ajaxConfig.url += i+'='+data[i]+'&'
                }
            } else {
                ajaxConfig['data'] = data;
            }
        }
        api_send_message({
            type: 'ajax-request',
            config: ajaxConfig,
            callback: function(res) {
                console.log(res)
                if (res.data.status == 401) {
                    terminal.displayOutput(res.data.response.message + '. use the <a class="command-exec" data-command="github auth --help">github auth</a> command to add personal access token and ')
                } else if (res.data.status == 403) {
                    terminal.displayOutput(res.data.response.message)
                } else if (res.data.statusText == 'OK' || [200, 204, 404].indexOf(res.data.status) > -1) {
                    callback(res.data.response, res.data.status)
                } else {
                    terminal.displayOutput(res.data.response.message)
                }
            }
        });
    }

    this.printRepos = function(items, owner, startIndex) {
        let pathNodeString = "<span>"+owner+" repos</span> | <code class='command-exec' data-command='github create repo -n '>new</code> | \
<code class='command-exec' data-command='github repo list -o '>change user</code> | \
<button class='command-btn' toggle-group='vscode'>vscode</button> | \
<button class='command-btn' toggle-group='delete'>delete</button>"
        let showStr = '<div class="content-title-1">' + pathNodeString +'</div>';
        showStr += '<div class="content-list-1">'
        items.forEach((item, i) => {
            let repoOwner = item.owner.login
            let itemName = item.name
            let itemFullName = '<b>'+item.full_name+'</b>'
            let execCmd = `github repo content -r ${itemName} -o ${repoOwner}`
            showStr += '<div><span class="col-30">'+(startIndex+i+1)+'</span><a class="command-exec help-span" data-command="'+execCmd+'">' + itemFullName + '</a>\
<a class="help-span hide" target="_blank" href="http://github.dev/'+item.full_name+'" toggle-vscode>vscode</a> \
<code class="help-span command-exec hide" data-command="github delete repo -r '+itemName+' -o '+repoOwner+'" toggle-delete>delete</code></div>'
        });
        showStr += '</div>'
        return showStr;
    }

    this.printRepoContents = function(items, owner, repo, path, type) {
        let showStr = '';
        
        let pathNodeString = "<a class='command-exec' data-command='github repo list'><b>All</b></a> / "
        if (path) {
            let execCmd = `github repo content -o ${owner} -r ${repo}`
            pathNodeString += "<a class='command-exec' data-command='" + execCmd+ "'><b>" + owner+'/'+repo + "</b></a>"

            let paths = path.split('/');
            let pathStr = '';
            paths.forEach(function (item_path, i) {
                if (i > 0) {
                    pathStr += '/';
                }
                pathNodeString += ' / ';
                pathStr += item_path
                let execCmd = `github repo content -o ${owner} -r ${repo} -p ${pathStr}`
                pathNodeString += "<a class='command-exec' data-command='" + execCmd+ "'><b>" + item_path + "</b></a>";
            });
        } else {
            let execCmd = `github repo content -o ${owner} -r ${repo}`
            pathNodeString += "<a class='command-exec' data-command='" + execCmd+ "'><b>" + owner +'/'+ repo + "</b></a>";
        }

        let operator = ` | <code class="command-exec" data-command="github create file -r ${repo} -p ">add</code>`
        operator += ` | <button class="command-btn" toggle-group="delete">delete</button>`
        showStr += '<div class="content-title-1">' + pathNodeString + ' (' + items.length + ') '+operator+'</div>';
        
        showStr += '<div class="content-list-1">'
        this.sortItems(items).forEach((item) => {
            let itemName = item.name
            if (item.type == 'dir') {
                itemName = '<b>'+itemName+'</b>'
            }
            let execCmd = `github repo content -o ${owner} -r ${repo} -p ${item.path}`;
            let cmdline = '<a class="command-exec help-span" data-command="'+execCmd+'">' + itemName + '</a>'
            let deleteCmd = `github delete file -o ${owner} -r ${repo} -p ${item.path}`
            if (item.type == 'dir') {
                deleteCmd += ' -d'
            } else {
                deleteCmd += ` -h ${item.sha}`
            }
            cmdline += `<code class="command-exec help-span hide" toggle-delete data-command="${deleteCmd}">delete</code>`
            if (type == 'rows') {
                cmdline = `<div>${cmdline}</div>`
            }
            showStr += cmdline;
        });
        showStr += '</div>'
        return showStr;
    }

    this.sortItems = function(items) {
        let dir_items = [];
        let file_items = [];
        items.forEach((item) => {
            if (item.type == 'dir') {
                dir_items.push(item)
            } else {
                file_items.push(item)
            }
        });
        return dir_items.concat(file_items)
    }

    this.checkAuth = function(owner, repo) {
        if (!this.conf.user) {

        }
        if (!this.conf.token) {
            
        }
    }
    
}
var jsonCmd = function () {
    this.options = {
        formate: {
            simple: "f",
            desc: "Useage: <code>json `{\"web\":\"terminal\"}` -f</code> use the json-parser as the default json engine.",
        },
    };
    this.desc = "Use json-parser json engine. Useage: <code>json `{\"hello\":\"world\"}`</code> or <code>json</code> automatically capture the json content in the page.";
    this.Exec = (command, terminal) => {
        let jsonStr = command.content.join('')
        if (!jsonStr) {
            jsonStr = document.body.innerText
        }
        // if (typeof command.options.formate == "undefined") {
        //     if (IsJsonString(jsonStr)) {
        //         return {
        //             type: 'command-list',
        //             data: [
        //                 "js `$('#split textarea').val('"+jsonStr+"');$('#split textarea').trigger('click');` -u http://json.parser.online.fr/",
        //             ]
        //         }
        //     } else {
        //         terminal.displayOutput("Invalid Json string.");
        //     }
        // } else {
            let jsonObject = null;
            try {
                jsonObject = JSON.parse(jsonStr)
            } catch (e) {
                jsonStr = jsonStr.replace(/\\\\/g, "\\").replace(/\\\"/g, '"');
                if (!jsonStr) jsonStr = '{}';
            }

            try {
                if (jsonObject === null) {
                    jsonObject = JSON.parse(jsonStr)
                }

                let jsonFormate = function (jsonObj, formateStr, i) {
                    let marginLeft = 20 * i;
                    let attrs = Object.keys(jsonObj);
                    let isArr = Array.isArray(jsonObj)
                    if (isArr) {
                        formateStr += '[';
                    } else {
                        formateStr += '{';
                    }
                    for (let attr in jsonObj) {
                        let attrVal = '';
                        if (jsonObj[attr] && typeof jsonObj[attr] == 'object') {
                            attrVal = jsonFormate(jsonObj[attr], '', i + 1);
                        } else {
                            attrVal = jsonObj[attr];
                            if (typeof attrVal == 'string') {
                                attrVal = '"' + attrVal.replace(/\\/g, "\\\\").replace(/\"/g, '\\"') + '"';
                            }
                        }

                        formateStr += '<br/><span style="margin-left:' + (marginLeft + 4) + 'px;width:20px;border-left:1px dashed #666;display: inline-block;height:8px;"></span>' + (isArr ? '' : '"' + attr + '": ') + attrVal;
                        if (attrs[attrs.length - 1] != attr) {
                            formateStr += ',';
                        }
                    }
                    formateStr += '<br/><span style="margin-left:' + (marginLeft) + 'px;">';
                    if (isArr) {
                        formateStr += ']';
                    } else {
                        formateStr += '}';
                    }
                    return formateStr;
                }
                terminal.displayOutput(jsonFormate(jsonObject, '', 0));
            } catch (e) {
                terminal.displayOutput("Parse json error:" + e);
            }
        // }
    }
}
var strCmd = function() {
    this.subCmds = {
        lower: {
            desc: "Make a string lowercase",
            Exec: function(command, terminal) {
                terminal.displayOutput(command.content[0].toLowerCase());
            }
        },
        upper: {
            desc: "Make a string uppercase",
            Exec: function(command, terminal) {
                terminal.displayOutput(command.content[0].toUpperCase());
            }
        },
        lcfirst: {
            desc: "Make a string's first character uppercase",
            Exec: function(command, terminal) {
                str = command.content[0];
                terminal.displayOutput(str.charAt(0).toLowerCase() + str.slice(1));
            }
        },
        ucfirst: {
            desc: "Make a string's first character uppercase",
            Exec: function(command, terminal) {
                str = command.content[0];
                terminal.displayOutput(str.charAt(0).toUpperCase() + str.slice(1));
            }
        },
        ucword: {
            desc: "Uppercase the first character of each word in a string",
            Exec: function(command, terminal) {
                str = command.content[0];
                str = str.replace(/(^([a-zA-Z\p{M}]))|([ -][a-zA-Z\p{M}])/g, function(s) {
                  return s.toUpperCase();
                })
                terminal.displayOutput(str);
            }
        },
        len: {
            desc: "String's length",
            Exec: function(command, terminal) {
                terminal.displayOutput(command.content[0].length);
            }
        },
        index: {
            desc: "The index of the sub string in the string",
            options: {
                substring: {
                    simple: "s",
                    desc: "The sub string",
                    dataType: "text",
                    required: true
                },
            },
            Exec: function(command, terminal) {
                terminal.displayOutput(command.content[0].indexOf(command.options.substring));
            }
        },
        split: {
            desc: "Convert a string to an array",
            options: {
                separator: {
                    simple: "s",
                    desc: "The boundary string",
                    dataType: "text",
                    required: true
                },
                limit: {
                    simple: "l",
                    desc: "The limit elements output",
                    dataType: "number"
                },
            },
            Exec: function(command, terminal) {
                if (typeof command.options.limit != 'undefined') {
                    terminal.displayOutput(command.content[0].split(command.options.separator, command.options.limit));
                } else {
                    terminal.displayOutput(command.content[0].split(command.options.separator));
                }
            }
        },
        replace: {
            desc: "Replace all occurrences of the search string with the replacement string",
            options: {
                search: {
                    simple: "s",
                    desc: "The value being searched for",
                    dataType: "text",
                    required: true
                },
                replace: {
                    simple: "r",
                    desc: "The replacement value that replaces found search values",
                    dataType: "text",
                    required: true
                },
                all: {
                    simple: "a",
                    desc: "Is replace all",
                    dataType: "bool"
                },
            },
            Exec: function(command, terminal) {
                if (typeof command.options.all != 'undefined') {
                    terminal.displayOutput(command.content[0].replaceAll(command.options.search, command.options.replace));
                } else {
                    terminal.displayOutput(command.content[0].replace(command.options.search, command.options.replace));
                }
            }
        },
        regex: {
            desc: "Perform a regular expression search and replace",
            options: {
                exp: {
                    simple: "e",
                    desc: "The regular expression",
                    dataType: "text",
                    required: true
                }
            },
            Exec: function(command, terminal) {
                const reg = new RegExp(command.options.exp)
                let match = reg.exec(command.content[0])
                terminal.displayOutput(match && match.length > 0 ? match[0] : 'null');
            }
        },
    }
}
var bookmarkCmd = function () {
    let self = this
    this.desc = "Browser bookmark <code>bookmark</code> or <code>bookmark 'query'</code>";
    this.conf = {
        path: 0
    }
    this._init_ = function(command, terminal) {
        $.extend(this.conf, terminal.getUserData('bookmark'));
    }

    this.setUserData = function(terminal, userData, callback) {
        let changed = false
        if (userData) {
            for (let i in userData) {
                if (typeof this.conf[i] == 'undefined' || this.conf[i] != userData[i]) {
                    changed = true
                }
                this.conf[i] = userData[i];
            }
        }
        if (changed) {
            terminal.setUserData('bookmark', this.conf, callback)
        }
    }

    this.getUserData = function(key, defaultVal) {
        if (!this.conf.hasOwnProperty(key)) {
            return defaultVal;
        }
        return this.conf[key]
    }

    this.subCmds = {
        ls: {
            desc: "list bookmarks or folders",
            Exec: (command, terminal) => {
                if (command.content.length == 0) {
                    command.content[0] = self.getUserData('path', 0)
                } else {
                    self.setUserData(terminal, {path: command.content[0]})
                }
                api_send_message({
                    type: "browser-bookmarks",
                    method: 'list',
                    content: command.content,
                    callback: function (msg) {
                        let results = msg.data;
                        let bookmarks = getPropertyVal(results, 'bookmark', []);
                        let paths = getPropertyVal(results, 'path', []);
                        let pathNodeString = '';
                        paths.forEach(function (pathNode, index) {
                            if (pathNode.id == '0') pathNode.title = 'Start';
                            if (index > 0) pathNodeString += ' / ';
                            pathNodeString += "<a class='command-exec' data-command='bookmark ls " + pathNode.id + "'><b>" + pathNode.title + "</b></a>";
                        });
                        let showStr = '<div class="content-title-1">' + pathNodeString + ' (' + bookmarks.length + ')</div>';
                        let folder = ''
                        let filer = ''
                        bookmarks.forEach(element => {
                            let isFolder = (element.hasOwnProperty('dateGroupModified') || element.parentId == '0') ? true : false;
                            if (isFolder) {
                                folder += "<li class='content-list-1'><a class='command-exec' data-command='bookmark ls " + element.id + "'><b>" + element.title + "</b></a></li>";
                            } else {
                                filer += "<li class='content-list-1'>" + (terminal.is_extension_page ? "<img src='" + api_getIcon(element.url) + "'> " : "") + "<a target='_blank' href='" + element.url + "'>" + element.title + "</a></li>";
                            }
                        });
                        showStr += folder+filer
                        terminal.displayOutput(showStr ? showStr : 'No result.')
                    }
                });
            }
        }
    };
    this.Exec = function (command, terminal) {
        if (command.content.length > 0) {
            api_send_message({
                type: "browser-bookmarks",
                method: 'query',
                content: command.content,
                callback: function (msg) {
                    let results = msg.data;
                    let showStr = '<div class="content-title-1">Query (' + results.length + ')</div>';
                    results.forEach(element => {
                        showStr += "<li class='content-list-1'>" + (terminal.is_extension_page ? "<img src='" + api_getIcon(element.url) + "'> " : "") + "<a target='_blank' href='" + element.url + "'>" + element.title + "</a></li>";
                    });
                    terminal.displayOutput(showStr ? showStr : 'No result.')
                }
            });
        } else {
            api_send_message({
                type: "browser-bookmarks",
                method: 'getRecent',
                content: command.content,
                options: { numberOfItems: 15 },
                callback: function (msg) {
                    let results = msg.data;
                    let showStr = "<div class='content-title-1'>Recent (15)</div>";
                    results.forEach(element => {
                        showStr += "<li class='content-list-1'>" + (terminal.is_extension_page ? "<img src='" + api_getIcon(element.url) + "'> " : "") + "<a target='_blank' href='" + element.url + "'>" + element.title + "</a></li>";
                    });
                    terminal.displayOutput(showStr ? showStr : 'No result.')
                }
            });
        }
    }
}
var cmdCmd = function () {
    var commonOptions = {
        hub: {
            simple: "h",
            desc: "the cmd hub url address.",
            default: "https://raw.githubusercontent.com/web-terminal/cmdhub/master/"
        }
    };
    this.subCmds = {
        add: {
            desc: "<code>cmd add [command]</code> add a command. <code>cmd add github</code> the github command will be adding.",
            options: commonOptions,
            Exec: function (command, terminal) {
                if (command.content.length > 0) {
                    let newCmd = command.content[0].trim();
                    // get the 
                    api_send_message({
                        type: "cmdhub", method: "add", options: command.options, newCmd: newCmd, callback: function (res) {
                            if (res.meta.code == 200) {
                                $.extend(terminal.thirdcmds, res.meta.data);
                                for (let cmd in terminal.thirdcmds) {
                                    terminal.all_commands[cmd] = terminal.thirdcmds[cmd]['index_func'];
                                }
                                terminal.displayOutput(newCmd + " added.");
                            } else {
                                terminal.displayErrorOutput(res.meta.msg);
                            }
                        }
                    });
                } else {
                    terminal.displayErrorOutput("Input the command names what you want to add.");
                }
            }
        },
        update: {
            desc: "<code>cmd update [command]</code> update a command. <code>cmd update github</code> to update the github commands latest version.",
            options: commonOptions,
            Exec: function (command, terminal) {
                if (command.content.length > 0) {
                    let newCmd = command.content[0].trim();
                    api_send_message({
                        type: "cmdhub", method: "update", options: command.options, newCmd: newCmd, callback: function (res) {
                            if (res.meta.code == 200) {
                                $.extend(terminal.thirdcmds, res.meta.data);
                                for (let cmd in self.thirdcmds) {
                                    terminal.all_commands[cmd] = terminal.thirdcmds[cmd]['index_func'];
                                }
                                terminal.displayOutput(newCmd + " updated.");
                            } else {
                                terminal.displayErrorOutput(res.meta.msg);
                            }
                        }
                    });
                } else {
                    terminal.displayErrorOutput("Input the command names what you want to update.");
                }
            }
        },
        delete: {
            desc: "<code>cmd delete [command]</code> delete a command. <code>cmd delete github</code> to delete github command.",
            options: commonOptions,
            Exec: function (command, terminal) {
                if (command.content.length > 0) {
                    let newCmd = command.content[0].trim();
                    api_send_message({
                        type: "cmdhub", method: "delete", options: command.options, newCmd: newCmd, callback: function (res) {
                            if (res.meta.code == 200) {
                                delete terminal.thirdcmds[newCmd];
                                delete terminal.all_commands[newCmd];
                                terminal.displayOutput(newCmd + " deleted.");
                            } else {
                                terminal.displayErrorOutput(res.meta.msg);
                            }
                        }
                    });
                } else {
                    terminal.displayErrorOutput("Input the command names what you want to delete.");
                }
            }
        },
        custom: {
            desc: 'Custom command',
            Exec: function (command, terminal) {
                if (command.content.length > 1) {
                    var newCmd = command.content[0];
                    // 命令名成不允许和系统命令相同
                    if (terminal.syscmds.hasOwnProperty(newCmd)) {
                        terminal.displayErrorOutput("Command " + newCmd + " is system command.");
                    } else {
                        api_send_message({
                            type: "cmdhub", method: "custom", newCmd: newCmd, cmdContent: command.content[1], callback: function (res) {
                                if (res.meta.code == 200) {
                                    $.extend(terminal.thirdcmds, res.meta.data);
                                    for (let cmd in terminal.thirdcmds) {
                                        terminal.all_commands[cmd] = terminal.thirdcmds[cmd]['index_func'];
                                    }
                                    terminal.displayOutput(newCmd + " added.");
                                } else {
                                    terminal.displayErrorOutput(res.meta.msg);
                                }
                            }
                        });
                    }
                }
            }
        }
    }
    this.desc = "This is Terminal's command manager. like nodejs npm.";
}
var cookieCmd = function () {
    let self = this;
    this.api_send_message = function(method, config, callback) {
        api_send_message({
            type: 'cookie',
            method: method,
            config: config,
            callback: callback
        });
    }
    this.desc = 'Use the <code>cookie</code> command to query and modify cookies, and to be notified when they change.'
    this.getDomain = function(domain) {
        if (domain.charAt(0) == '.') {
            domain = domain.substr(1)
        }
        return domain;
    }
    this.getBaseDomain = function(domain) {
        domainArr = domain.split('.');
        if (domainArr.length >= 2) {
            domain = domainArr[domainArr.length-2]+'.'+domainArr[domainArr.length-1];
        }
        return domain;
    }
    this.getUrl = function(cookie) {
        const protocol = cookie.secure ? "https:" : "http:";
        return `${protocol}//${cookie.domain}${cookie.path}`;
    }
    this.showPath = function(config, cookie) {
        let pathNodeString = `<a class='command-exec' data-command='cookie ls -a'><b>All</b></a>`;
        let clearCommand = 'cookie clear -a';
        if (config.domain) {
            pathNodeString += ` / <a class='command-exec' data-command='cookie ls -d ${config.domain}'><b>${config.domain}</b></a>`;
            clearCommand = `cookie clear -d ${config.domain}`;
        }
        if (config.name) {
            pathNodeString += ` / <a class='command-exec' data-command='cookie ls -d ${config.domain} -n ${config.name}'><b>${config.name}</b></a>`;
            clearCommand = '';
            if (cookie) {
                let url = self.getUrl(cookie);
                clearCommand = `cookie remove -n ${cookie.name} -u ${url} -i ${cookie.storeId}`;
            }
            
        }

        if (clearCommand) {
            pathNodeString += `<code class="command-exec float-right" data-command="${clearCommand}">clear</code>`;
        }

        return pathNodeString;
    }
    this.showDomains = function(config, items) {
        let domains = {};
        let showStr = '';
        let count = 0;
        items.forEach(function(item, key) {
            let domain = self.getBaseDomain(item.domain)
            if (domains.hasOwnProperty(domain)) {
                return;
            }
            count++;
            domains[domain] = domain
            showStr += `<li class='content-list-1'>\
<a class="command-exec" data-command="cookie ls -d ${domain}" title="domain"><b>${domain}</b></a>\
<code class="command-exec float-right" data-command="cookie clear -d ${domain}">remove</code>\
</li>`;
        });
        showStr = '<div class="content-title-1"> ' + self.showPath(config) + ' (' + count + ')</div>' + showStr;
        return showStr;
    }
    this.showItems = function(config, items) {
        let showStr = '<div class="content-title-1">'+self.showPath(config)+' (' + items.length + ')</div>';
        items.forEach(function(item, key) {
            let url = self.getUrl(item)
            let domain = self.getDomain(item.domain)
            showStr += `<li class='content-list-1'>\
<a class="command-exec" data-command="cookie ls -d ${domain}" title="name"><b>${domain}</b></a> / \
<a class="command-exec" data-command="cookie ls -d ${domain} -n ${item.name}" title="name">${item.name}</a>\
<code class="command-exec float-right" data-command="cookie remove -n ${item.name} -u ${url} -i ${item.storeId}">remove</code></li>`;
        });
        return showStr;
    }
    this.showItemDetails = function(config, items) {
        let showStr = '<div class="content-title-1">'+(items.length > 0 ? self.showPath(config, items[0]) : self.showPath(config))+'</div>';
        items.forEach(function(item, key) {
            let domain = self.getDomain(item.domain)
            showStr += `<p>domain: <code>${domain}</code></p>`;
            showStr += `<p>expirationDate: <code>${item.expirationDate}</code></p>`;
            showStr += `<p>hostOnly: <code>${item.hostOnly}</code></p>`;
            showStr += `<p>httpOnly: <code>${item.httpOnly}</code></p>`;
            showStr += `<p>name: <code>${item.name}</code></p>`;
            showStr += `<p>path: <code>${item.path}</code></p>`;
            showStr += `<p>sameSite: <code>${item.sameSite}</code></p>`;
            showStr += `<p>secure: <code>${item.secure}</code></p>`;
            showStr += `<p>session: <code>${item.session}</code></p>`;
            showStr += `<p>storeId: <code>${item.storeId}</code></p>`;
            showStr += `<p>value: <code>${item.value}</code></p>`;
        });
        return showStr;
    }
    this.subCmds = {
        ls: {
            desc: 'Retrieves all cookies',
            options: {
                all: {
                    simple: 'a',
                    desc: 'List all cookie domains.',
                    dataType: 'bool'
                },
                domain: {
                    simple: 'd',
                    desc: 'Restricts the retrieved cookies to those domains match or are subdomains of this one.',
                    dataType: 'string'
                },
                name: {
                    simple: 'n',
                    desc: 'Filters the cookies by name.',
                    dataType: 'string'
                },
            },
            Exec: (command, terminal) => {
                let config = {}
                if (command.options.domain) {
                    config.domain = command.options.domain;
                }

                if (!command.options.all && !config.domain) {
                    const urlObj = new URL(location.href);
                    config.domain = self.getBaseDomain(urlObj.hostname);
                }

                if (command.options.name) {
                    config.name = command.options.name;
                }

                self.api_send_message('getAll', config, function(res) {
                    let showStr = '';
                    if (config.name) {
                        // 展示cookie详情
                        showStr = self.showItemDetails(config, res.data);
                    } else if (command.options.all) {
                        // 展示domain下的cookie
                        showStr = self.showDomains(config, res.data);
                    } else if (config.domain) {
                        // 展示domain
                        showStr = self.showItems(config, res.data);
                    }

                    terminal.displayOutput(showStr);
                });
            },
        },
        clear: {
            desc: 'Clear all cookies',
            options: {
                domain: {
                    simple: 'd',
                    desc: 'Restricts the retrieved cookies to those domains match or are subdomains of this one.',
                    dataType: 'string'
                },
                all: {
                    simple: 'a',
                    desc: 'Clear all domain cookies.',
                    dataType: 'bool'
                }
            },
            Exec: (command, terminal) => {
                let config = {}
                if (command.options.domain) {
                    config.domain = command.options.domain;
                }

                if (!config.domain && !command.options.all) {
                    const urlObj = new URL(location.href);
                    config.domain = urlObj.hostname;
                }

                self.api_send_message('getAll', config, function(res) {
                    res.data.forEach(function(item) {
                        let option = {
                            name: item.name,
                            url: self.getUrl(item),
                            storeId: item.storeId
                        }
                        self.api_send_message('remove', option, function(ret) {
                            if (ret.data.url) {
                                let domain = self.getDomain(ret.data.url);
                                terminal.displayOutput(`remove <code><b>${domain}</b></code> / <code>${ret.data.name}</code> OK`);
                            }
                        });
                    });
                });
            },
        },
        list: {
            desc: 'Usage: <code>cookie getall</code> Retrieves all cookies from a single cookie store that \
match the given information. The cookies returned will be sorted, with those with the longest path first. \
If multiple cookies have the same path length, those with the earliest creation time will be first.',
            options: {
                domain: {
                    simple: 'd',
                    desc: 'Restricts the retrieved cookies to those whose domains match or are subdomains of this one.',
                    dataType: 'string'
                },
                name: {
                    simple: 'n',
                    desc: 'The name of the cookie to access.',
                    dataType: 'string'
                },
                path: {
                    simple: 'p',
                    desc: 'Restricts the retrieved cookies to those whose path exactly matches this string.',
                    dataType: 'string'
                },
                secure: {
                    simple: 's',
                    desc: 'Filters the cookies by their Secure property.',
                    dataType: 'bool'
                },
                session: {
                    simple: 'S',
                    desc: 'Filters out session vs. persistent cookies.',
                    dataType: 'bool'
                },
                url: {
                    simple: 'u',
                    desc: "Restricts the retrieved cookies to those that would match the given URL.",
                    dataType: 'string'
                },
                storeId: {
                    simple: 'i',
                    desc: "The cookie store to retrieve cookies from. If omitted, the current execution context's cookie store will be used.",
                    dataType: 'string'
                }
            },
            Exec: (command, terminal) => {
                self.api_send_message('getAll', command.options, function(res) {
                    terminal.displayOutput(res.data);
                });
            },
        },
        get: {
            desc: 'Retrieves information about a single cookie. If more than one cookie of the same name exists for the given URL, \
the one with the longest path will be returned. For cookies with the same path length, the cookie with the earliest creation time will be returned.',
            options: {
                name: {
                    simple: 'n',
                    desc: 'The name of the cookie to access.',
                    required: true,
                    dataType: 'string'
                },
                url: {
                    simple: 'u',
                    desc: "The URL with which the cookie to access is associated. \
This argument may be a full URL, in which case any data following the URL path (e.g. the query string) \
is simply ignored. If host permissions for this URL are not specified in the manifest file, the API call will fail.",
                    required: true,
                    dataType: 'string'
                },
                storeId: {
                    simple: 'i',
                    desc: "The ID of the cookie store in which to look for the cookie. By default, \
the current execution context's cookie store will be used.",
                    dataType: 'string'
                }
            },
            Exec: (command, terminal) => {
                self.api_send_message('get', command.options, function(res) {
                    terminal.displayOutput(res.data);
                });
            },
        },
        stores: {
            desc: 'Usage: <code>urlrule list</code> List the rules that has set',
            Exec: (command, terminal) => {
                self.api_send_message('getAllCookieStores', {}, function(res) {
                    terminal.displayOutput(res.data);
                });
            },
        },
        set: {
            desc: 'Sets a cookie with the given cookie data; may overwrite equivalent cookies if they exist.',
            options: {
                domain: {
                    simple: 'd',
                    desc: 'The domain of the cookie. If omitted, the cookie becomes a host-only cookie.',
                    dataType: 'string'
                },
                expirationDate: {
                    simple: 'e',
                    desc: 'The expiration date of the cookie as the number of seconds since the UNIX epoch. \
If omitted, the cookie becomes a session cookie.',
                    dataType: 'number'
                },
                httpOnly: {
                    simple: 'h',
                    desc: 'Whether the cookie should be marked as HttpOnly. Defaults to false.',
                    dataType: 'bool'
                },
                name: {
                    simple: 'n',
                    desc: 'The name of the cookie to access.',
                    dataType: 'string'
                },
                path: {
                    simple: 'p',
                    desc: 'Restricts the retrieved cookies to those whose path exactly matches this string.',
                    dataType: 'string'
                },
                sameSite: {
                    simple: 'a',
                    desc: "The cookie's same-site status. Defaults to \"unspecified\", i.e., if omitted, \
the cookie is set without specifying a SameSite attribute.",
                    dataType: 'string'
                },
                secure: {
                    simple: 's',
                    desc: 'Whether the cookie should be marked as Secure. Defaults to false.',
                    dataType: 'bool'
                },
                session: {
                    simple: 'S',
                    desc: 'Filters out session vs. persistent cookies.',
                    dataType: 'bool'
                },
                url: {
                    simple: 'u',
                    desc: "The request-URI to associate with the setting of the cookie. This value can affect the default \
domain and path values of the created cookie. If host permissions for this URL are not specified in the manifest file, the API call will fail.",
                    required: true,
                    dataType: 'string'
                },
                storeId: {
                    simple: 'i',
                    desc: "The ID of the cookie store in which to set the cookie. By default, \
the cookie is set in the current execution context's cookie store.",
                    dataType: 'string'
                },
                value: {
                    simple: 'v',
                    desc: "The value of the cookie. Empty by default if omitted.",
                    dataType: 'string'
                }
            },
            Exec: (command, terminal) => {
                self.api_send_message('set', command.options, function(res) {
                    terminal.displayOutput(res.data);
                })
            },
        },
        remove: {
            desc: 'Deletes a cookie by name.',
            options: {
                name: {
                    simple: 'n',
                    desc: "The name of the cookie to access.",
                    required: true,
                    dataType: 'string'
                },
                url: {
                    simple: 'u',
                    desc: "The URL with which the cookie to access is associated. This argument may be a full URL, \
in which case any data following the URL path (e.g. the query string) is simply ignored. \
If host permissions for this URL are not specified in the manifest file, the API call will fail.",
                    required: true,
                    dataType: 'string'
                },
                storeId: {
                    simple: 'i',
                    desc: "The ID of the cookie store in which to look for the cookie. \
By default, the current execution context's cookie store will be used.",
                    dataType: 'string'
                },
            },
            Exec: (command, terminal) => {
                self.api_send_message('remove', command.options, function(res) {
                    terminal.displayOutput(res.data);
                })
            },
        }
    }
}
var cronCmd = function () {
    this.options = {
        list: {
            simple: "l",
            desc: "Useage: <code>cron -l</code> show all the crontab tasks"
        },
        add: {
            simple: "a",
            desc: "Useage: <code>cron `00 * * * * *` `js console.log(123);` -a</code> Add a new contab task"
        },
        delete: {
            simple: "D",
            desc: "Useage: <code>cron 4f0wr4ynbs80 -D</code> delete the specified contab task"
        },
        showType: {
            simple: "s",
            dataType: "enum",
            options: ["background", "frontend"],
            default: "frontend",
            desc: "Useage: <code>cron 4f0wr4ynbs80 -s background</code> Update the show type when cron job excute."
        },
        openType: {
            simple: "o",
            dataType: "enum",
            default: "auto-open",
            desc: "Useage: <code>cron 4f0wr4ynbs80 -o auto-open</code> Update the show type when cron job excute.",
            options: ["auto-open", "only-open", "finished-close"],
        },
        enabled: {
            simple: "e",
            dataType: "bool",
            default: 1,
            desc: "Useage: <code>cron 4f0wr4ynbs80 -e 0</code> Update the enabled status. the options is 0 or 1"
        },
        url: {
            simple: "u",
            desc: "Useage: <code>cron 4f0wr4ynbs80 -u http://www.google.com</code> Update the host page url address."
        },
        rule: {
            simple: "r",
            desc: "Useage: <code>cron 4f0wr4ynbs80 -r `01 * * * * *`</code> Update the rule of cron."
        },
        command: {
            simple: "c",
            desc: "Useage: <code>cron 4f0wr4ynbs80 -c `time -t`</code> Update the executor command."
        },
    }
    this.desc = "Useage: <code>cron `*/3 * * * * *` \"js `console.log('hello WebTerminal!')`\" -a</code>";
    this.defaultOption = "list";
    this.Exec = function (command, cmdwin) {
        if (command.options.hasOwnProperty("list")) {
            api_send_message({
                type: "cron-job",
                options: {
                    type: "list"
                },
                callback: function (msg) {
                    let cronJobs = msg.data;
                    if (JSON.stringify(cronJobs) == '{}') {
                        cmdwin.displayOutput("You haven't added cron tasks.")
                    } else {
                        let showStr = '<table width="100%"><tr><th>id</th><th>rule</th><th>cmds</th><th>enabled</th><th>showType</th><th>openType</th></tr>';
                        for (let id in cronJobs) {
                            showStr += '<tr title="' + cronJobs[id]['url'] + '"><td><code class="command-exec" data-command="cron ' + id + '">' + id + '</code></td>';
                            showStr += '<td><code class="command-exec" data-command="cron ' + id + ' -r `' + cronJobs[id]['rule'] + '`">' + cronJobs[id]['rule'] + '</code></td>';
                            showStr += '<td><code class="command-exec" data-type="base64" data-command="' + window.btoa('cron ' + id + ' -c `' + cronJobs[id]['cmds'].join('<br/>') + '`') + '">' + cronJobs[id]['cmds'].join('<br/>') + '</code></td>';
                            showStr += '<td><code class="command-exec" data-command="cron ' + id + ' -e ' + cronJobs[id]['enabled'] + '">' + cronJobs[id]['enabled'] + '</code></td>';
                            showStr += '<td><code class="command-exec" data-command="cron ' + id + ' -s ' + cronJobs[id]['showType'] + '">' + cronJobs[id]['showType'] + '</code></td>';
                            showStr += '<td><code class="command-exec" data-command="cron ' + id + ' -o ' + cronJobs[id]['openType'] + '">' + cronJobs[id]['openType'] + '</td>';
                            // showStr += '<td>'+cronJobs[id]['times']+'</td>';
                            showStr += '</tr>';
                        }
                        showStr += '</table>'
                        cmdwin.displayOutput(showStr);
                    }
                }
            });
        } else if (command.options.hasOwnProperty("add")) {
            var cronRule = command.content[0];
            command.content.splice(0, 1)
            var options = {
                type: "add",
                rule: cronRule,
                cmds: command.content
            }
            if (command.options.hasOwnProperty('showType') && ["background", "frontend"].indexOf(command.options.showType) > -1) {
                options['showType'] = command.options.showType
            }
            if (command.options.hasOwnProperty('openType') && ["auto-open", "only-open", "finished-close"].indexOf(command.options.openType) > -1) {
                options['openType'] = command.options.openType
            }
            if (command.options.hasOwnProperty('url')) {
                options['url'] = command.options.url
            }
            // console.log("add cron:", cronRule, command.content)
            api_send_message({
                type: "cron-job",
                options: options,
                callback: function (msg) {
                    cmdwin.handleInput('cron -l');
                }
            });
        } else if (command.options.hasOwnProperty("delete")) {
            let id = command.options.delete;
            if (typeof id == "boolean") id = command.content[0];
            api_send_message({
                type: "cron-job",
                options: {
                    type: "delete",
                    id: id
                },
                callback: function (msg) {
                    cmdwin.handleInput('cron -l');
                }
            });
        } else {
            var options = {
                type: "update"
            }
            if (command.content.length > 0) {
                options['id'] = command.content[0];
            }
            if (command.options.hasOwnProperty('enabled')) {
                options['enabled'] = command.options.enabled ? true : false;
            }
            if (command.options.hasOwnProperty('showType') && ["background", "frontend"].indexOf(command.options.showType) > -1) {
                options['showType'] = command.options.showType;
            }
            if (command.options.hasOwnProperty('openType') && ["auto-open", "only-open", "finished-close"].indexOf(command.options.openType) > -1) {
                options['openType'] = command.options.openType;
            }
            if (command.options.hasOwnProperty('url') && isURL(command.options.url)) {
                options['url'] = command.options.url;
            }
            if (command.options.hasOwnProperty('rule')) {
                options['rule'] = command.options.rule;
            }
            if (command.options.hasOwnProperty('command')) {
                options['cmds'] = typeof command.options.command == 'string' ? [command.options.command] : command.options.command;
            }

            api_send_message({
                type: "cron-job",
                options: options,
                callback: function (msg) {
                    cmdwin.handleInput('cron -l');
                }
            });
        }
    }
}
var curlCmd = function () {
    this.options = {
        user: {
            simple: "u",
            desc: "Usage: <code>curl http://api.xxx.com -u username:password</code> Server user and password"
        },
        header: {
            simple: "H",
            desc: "Usage: <code>curl http://api.xxx.com -H header1:v1 -H header2:v2</code> Pass custom header(s) to server"
        },
        get: {
            simple: "G",
            desc: "Usage: <code>curl http://api.xxx.com -G</code> Put the post data in the URL and use GET"
        },
        timeout: {
            desc: "Usage: <code>curl http://api.xxx.com --timeout</code> Set the seconds request timeout."
        },
        data: {
            simple: "d",
            desc: 'Usage: <code>curl http://api.xxx.com -d {"k1":v1, "k2":"v2"}</code> Or <code>curl http://api.xxx.com -d k1=v1&k2=v2</code> HTTP POST data'
        },
        include: {
            simple: "i",
            desc: "Usage: <code>curl http://api.xxx.com -i</code> Include protocol response headers in the output"
        }
    };
    this.desc = "Usage: <code>curl http://api.xxx.com -u username:password -d `{\"field\": \"value\"}` -i </code> Or <code>curl http://api.xxx.com -u username:password -d k1=v1&k2=v2&k3=v3 -i</code>";
    this.defaultOption = "get";
    this.Exec = function (command, cmdwin) {
        if (command.content.length < 1) {
            cmdwin.displayOutput("no url input.");
        } else {
            let ajaxConfig = {
                url: command.content[0],
                method: 'GET',
                responseType: 'text'
            }
            // data
            if (command.options.hasOwnProperty('data')) {
                ajaxConfig['method'] = 'POST';

                if (typeof command.options.data == 'object') {
                    let data = {};
                    for (let i in command.options.data) {
                        let element = command.options.data[i];
                        let keyval = element.indexOf(':') > -1 ? element.split(':') : element.split('=');
                        // console.log(keyval)
                        if (keyval.length > 1) {
                            data[$.trim(keyval[0])] = $.trim(keyval[1]);
                        } else {
                            data[i] = element;
                        }
                    }
                    ajaxConfig['data'] = data;
                } else {
                    ajaxConfig['data'] = command.options.data;
                }
            }
            // header
            if (command.options.hasOwnProperty('header')) {
                let header = {};
                if (typeof command.options.header == 'string') {
                    command.options.header = [command.options.header];
                }
                command.options.header.forEach(element => {
                    let keyval = element.indexOf(':') > -1 ? element.split(':') : element.split('=');
                    header[$.trim(keyval[0])] = keyval.length > 1 ? $.trim(keyval[1]) : true;
                    // console.log(keyval, header)
                });
                ajaxConfig['headers'] = header;
            }
            // user
            if (command.options.hasOwnProperty('user')) {
                let keyval = command.options.user.split(':')
                ajaxConfig['username'] = keyval.length > 0 ? keyval[0] : '';
                ajaxConfig['password'] = keyval.length > 1 ? keyval[1] : '';
            }
            // get
            if (command.options.hasOwnProperty('get')) {
                ajaxConfig['method'] = 'GET';
            }
            // timeout
            if (command.options.hasOwnProperty('timeout')) {
                ajaxConfig['timeout'] = command.options['timeout'];
            }
            // ajax-request
            api_send_message({
                type: 'ajax-request',
                config: ajaxConfig,
                callback: function (res) {
                    console.log(res, ajaxConfig)
                    if (command.options.hasOwnProperty('include')) {
                        cmdwin.displayOutput('============================ Request ========================');
                        for (let option in ajaxConfig) {
                            if (typeof ajaxConfig[option] != 'string') {
                                ajaxConfig[option] = JSON.stringify(ajaxConfig[option]);
                            }
                            cmdwin.displayOutput(option + ': ' + ajaxConfig[option], true);
                        }
                        cmdwin.displayOutput('============================ Response ========================');
                        cmdwin.displayOutput('Status: ' + res.data.status + ' ' + res.data.statusText);
                        cmdwin.displayOutput('Body: ' + res.data.response);
                    } else {
                        cmdwin.displayOutput(res.data.response, true)
                    }
                }
            });
        }
    }
}
var historyCmd = function () {
    let self = this
    this.desc = "Browser history <code>history</code> or <code>history 'query'</code>";
    this.options = {
        startTime: {
            simple: 's',
            desc: 'Limit results to those visited after this date, represented in milliseconds since the epoch. If not specified, this defaults to 24 hours in the past.'
        },
        endTime: {
            simple: 'e',
            desc: 'Limit results to those visited before this date, represented in milliseconds since the epoch.'
        },
        maxResults: {
            simple: 'm',
            desc: 'The maximum number of results to retrieve.',
            dataType: 'number',
            default: 20,
        },
    }
    this.Exec = function (command, terminal) {
        let options = command.options
        options.text = ''
        if (command.content.length > 0) {
            options.text = command.content[0]
            options.startTime = 0
        }
        api_send_message({
            type: "browser-history",
            options: options,
            callback: function (msg) {
                let results = self.showListView(msg.data, terminal.is_extension_page, options.text, options.maxResults)
                terminal.displayOutput(results)
            }
        });
    }

    this.showListView = function(items, hasimage, input, limit) {
        let showStr = '<div class="content-title-1">Result ' + items.length + '</div>';

        let lastVisit = 0
        showStr += '<div class="content-list-1">'
        if (typeof items == 'object' && items.length > 0) {
            items.forEach((item, i) => {
                lastVisit = item.lastVisitTime
                let urlSpliter = item.url.split('/')
                let imageView = hasimage ? "<img style='background:#fff;' src='" + api_getIcon(item.url) + "'> " : ""
                showStr += `<li class='content-list-1'><span>${formateTime(new Date(lastVisit))}</span><span class="help-span"></span>${imageView}\
<a target='_blank' href='${item.url}'>${item.title}</a>\
<span class="help-span gray">${urlSpliter[2]}</span>\
</li>`;
            });
            
        } else {
            return showStr +'<span class="help-span">No result.</span></div>'
        }
        
        showStr += '</div>'

        if (lastVisit > 0 && items.length == limit) {
            let end = parseInt(lastVisit)
            let start = 0
    
            let cmd = `history ${input} -e ${end} -s ${start}`
            showStr += '<div class="content-list-1"><a class="command-exec" data-command="'+cmd+' "> -------more------- </a></div>';
        }

        return showStr;
    }
}
var jsCmd = function () {
    this.options = {
        current: {
            simple: ["c"],
            desc: "Useage: <code>js `alert('hello web terminal.')` -c</code> Or <code>js `alert('hello web terminal.')`</code> to exec js code at the current page."
        },
        url: {
            simple: "u",
            desc: "Useage: <code>js `alert('hello web terminal.')` -u https://www.google.com</code> to exec js code at the specified url page."
        },
        file: {
            simple: "f",
            desc: "Useage: <code>js https://buttons.github.io/buttons.js -f</code> to exec js file at the specified page."
        },
        "run-delay": {
            simple: "r",
            desc: "Useage: <code>js `alert('hello web terminal.')` -u https://www.google.com -r 1000</code> to exec js code at the specified url page after 1000ms.",
        },
        "delete-delay": {
            simple: "d",
            desc: "Useage: <code>js `alert('hello web terminal.')` -u https://www.google.com -d 500</code> to exec js code at the specified url page and delete this page tab when finished after 500ms."
        },
        "active-open": {
            simple: "a",
            dataType: 'bool',
            desc: "Useage: <code>js `alert('hello web terminal.')` -u https://www.google.com -a</code> to exec js code at the specified url page and active this page tab."
        },
        "background-open": {
            simple: "b",
            dataType: 'bool',
            desc: "Useage: <code>js `alert('hello web terminal.')` -u https://www.google.com -b</code> to exec js code at the specified url page in background tab."
        },
        "new": {
            simple: "n",
            dataType: 'bool',
            desc: "Useage: <code>js `alert('hello web terminal.')` -u https://www.google.com -n</code> to exec js code at the specified url page in new tab."
        }
    }
    this.desc = "This command is very powerful. you can use javascript code to finish your task, and jquery also be supported. eg: <code>js `$('#name').val('ok');var a = 1;alert(a);`</code>"
    this.Exec = (command, cmdwin) => {
        let execjs = function (content) {
            api_send_message({
                type: "js",
                options: command.options,
                content: content,
                feed_data: cmdwin.feed_data,
                callback: function (msg) {
                    cmdwin.displayOutput(msg.response);
                }
            });
        }
        if (command.options.hasOwnProperty('file')) {
            let url = isURL(command.options.file) ? command.options.file : command.content[0];
            if (isURL(url)) {
                let ajaxConfig = {
                    url: url,
                    type: 'GET',
                    async: true,
                    dataType: 'text'
                }
                api_send_message({
                    type: 'ajax-request',
                    config: ajaxConfig,
                    callback: function (res) {
                        execjs([res.data.xhr.responseText]);
                    }
                });
            }
        } else {
            execjs(command.content);
        }

    }
}
var ruleCmd = function () {
    let self = this;
    this.api_send_message = function(method, config, callback) {
        api_send_message({
            type: 'rule',
            method: method,
            config: config,
            callback: callback
        });
    }
    this.desc = 'The <code>rule</code> command is used to block or modify network requests by specifying \
declarative rules. This lets extensions modify network requests without intercepting them and viewing \
their content, thus providing more privacy.'
    this.subCmds = {
        list: {
            desc: 'Usage: <code>rule list</code> List the rules that has set',
            // options: {
            //     all: {
            //         simple: 'a',
            //         desc: 'List all rules',
            //         dataType: 'bool',
            //     },
            //     dynamic: {
            //         simple: 'd',
            //         desc: 'List the dynamic rules',
            //         dataType: 'bool'
            //     }
            // },
            Exec: (command, terminal) => {
                console.log(command)
                // for (let option in command.options) {
                //     switch (option) {
                //         case "dynamic":
                //             self.api_send_message('getDynamicRules', {}, function(res) {
                //                 terminal.displayOutput(res.data);
                //             })
                //             break;
                //     }
                // }
                self.api_send_message('getDynamicRules', {}, function(res) {
                    terminal.displayOutput(res.data);
                });
            },
        },
        add: {
            desc: 'Usage: <code>rule add</code> Add url rule settings',
            options: {
                id: {
                    simple: 'i',
                    desc: 'An id which uniquely identifies a rule. Mandatory and should be >= 1.',
                    required: true,
                    dataType: 'number',
                },
                action: {
                    simple: 'a',
                    desc: "The action to take if this rule is matched. \
<a target='_blank' href='https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/#type-RuleAction'>data type doc</a>",
                    required: true,
                    dataType: 'json'
                },
                condition: {
                    simple: 'c',
                    desc: "The condition under which this rule is triggered. \
<a target='_blank' href='https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/#type-RuleCondition'>data type doc</a>",
                    required: true,
                    dataType: 'json'
                },
                priority: {
                    simple: 'p',
                    desc: 'The priority of rule',
                    dataType: 'number',
                },
            },
            Exec: (command, terminal) => {
                let rule = {
                    id: command.options.id,
                    priority: command.options.priority || 1,
                    action: command.options.action,
                    condition: command.options.condition,
                }

                let config = {
                    addRules: [rule]
                }
                
                self.api_send_message('updateDynamicRules', config, function(res) {
                    terminal.displayOutput(res.data);
                })
            },
        },
        remove: {
            desc: 'Usage: <code>rule remove</code> remove url rule settings',
            options: {
                ids: {
                    simple: 'i',
                    desc: 'The remove id list. such as <code>rule remove -i [1,2]</code> it will delete the rules with id 1 and 2.',
                    required: true,
                    dataType: 'json',
                },
            },
            Exec: (command, terminal) => {

                let config = {
                    removeRuleIds: command.options.ids
                }
                
                self.api_send_message('updateDynamicRules', config, function(res) {
                    terminal.displayOutput(res.data);
                })
            },
        }
    }
}
var storageCmd = function () {
    var self = this;
    this.desc = "storage ";
    this.commonOptions = {
        area: {
            desc: "storage area",
            simple: "a",
            dataType: "enum",
            options: ["local", "remote"],
            default: "local"
        }
    };
    this.commonExec = function (command, cmdwin, method) {
        api_send_message({
            type: "storage",
            method: method,
            options: command.options,
            content: command.content,
            callback: function (msg) {
                cmdwin.displayOutput(msg.meta.data);
            }
        });
    };

    this.subCmds = {
        get: {
            desc: "get storage by keys",
            options: self.commonOptions,
            Exec: function (command, cmdwin) {
                self.commonExec(command, cmdwin, 'get');
            }
        },
        set: {
            desc: "set storage by key value",
            options: self.commonOptions,
            Exec: function (command, cmdwin) {
                self.commonExec(command, cmdwin, 'set');
            }
        }
    }
}
var lsCmd = function () {
    this.desc = 'Show the current commands or options.';
    this.Exec = function (command, cmdwin) {
        let help = new helpCmd();
        let showStr = "";
        if (command.content.length > 0) {
            if (cmdwin.all_commands.hasOwnProperty(command.content[0])) {
                commandObject = new cmdwin.all_commands[command.content[0]]();
                // has sub command?
                showStr += help.printCmdDetail('', commandObject, '');
            }
        } else {
            showStr += help.printCmds(cmdwin.all_commands);
        }
        cmdwin.displayOutput(showStr)
    }
}

// var cdCmd = function () {
//     this.desc = 'Change to the command space.';
//     this.Exec = function (command, cmdwin) {
//         if (command.content.length > 0) {
//             if (!command.content[0].trim()) {
//                 cmdwin.setPrompt("$ ");
//                 cmdwin.displayOutput("")
//             } else if (cmdwin.all_commands.hasOwnProperty(command.content[0])) {
//                 cmdwin.setPrompt("$" + command.content[0] + " ");
//                 cmdwin.displayOutput("")
//             } else {
//                 cmdwin.displayErrorOutput("invalid command " + command.content[0]);
//             }
//         } else {
//             cmdwin.setPrompt("$ ");
//             cmdwin.displayOutput("")
//         }
//     }
// }

var clearCmd = function () {
    this.options = {
        screen: {
            simple: "s",
            desc: "clear screen. <code>clear -s</code>",
        },
        history: {
            simple: "h",
            desc: "clear history. <code>clear -h</code>",
        }
    }
    this.desc = 'clear datas. useage <code>clear</code>';
    this.defaultOption = 'screen';
    this.Exec = function (command, cmdwin) {
        for (let option in command.options) {
            switch (option) {
                case "screen":
                    // clear screen
                    cmdwin.clearScreen();
                    break;
                case "history":
                    // clear history
                    cmdwin.cmd_stack.empty();
                    cmdwin.cmd_stack.reset();
                    cmdwin.displayOutput('Command history cleared. ');
                    break;
                default:
                    cmdwin.displayOutput('Place use the right option.');
            }
        }
    }
}

var themeCmd = function () {
    this.options = {
        dark: {
            simple: "d",
            desc: "typical mode",
        },
        white: {
            simple: "w",
            desc: "舒适模式",
        }
    };

    this.desc = "Theme controller";

    this.Exec = function (command, cmdwin) {
        cmdwin.invert();
        cmdwin.displayOutput('Shazam.');
    }
}

var helpCmd = function () {
    var self = this;
    this.desc = "Show the command how to use. eg. <code class='command-exec' data-command='help time'>help time</code> or <code class='command-exec' data-command='time --help'>time --help</code>"
    this.printCmdDetail = function (cmd, commandConfig, commandLink) {
        let showStr = '<ul class="help-doc">';
        // desc
        if (commandConfig.hasOwnProperty('desc'))
            showStr += "<li>" + (cmd ? "<code class='command-exec' data-command='" + commandLink + "'>" + cmd + "</code>" : '') + '<span class="help-span">' + commandConfig.desc + "</span></li>";
        if (commandConfig.hasOwnProperty('options')) {
            for (let option in commandConfig.options) {
                showStr += '<li>'
                showStr += '<span class="help-span bold">' + (commandConfig.options[option].hasOwnProperty('simple') ? ('-' + commandConfig.options[option]['simple'][0]) : '&nbsp;&nbsp;') + '</span>'
                showStr += '<span class="help-span bold">--' + option + '</span>';
                if (getPropertyVal(commandConfig.options[option], "required")) {
                    showStr += '<span class="help-span red_highlight">Required</span>'
                }
                if (getPropertyVal(commandConfig.options[option], "dataType")) {
                    if (commandConfig.options[option].dataType == 'enum' && commandConfig.options[option].hasOwnProperty('options')) {
                        commandConfig.options[option].dataType = 'enum(' + commandConfig.options[option]['options'].join(',') + ')';
                    }
                    showStr += '<span class="help-span green_highlight">dataType=' + commandConfig.options[option].dataType + '</span>'
                }
                if (commandConfig.options[option].hasOwnProperty('default')) {
                    showStr += '<span class="help-span green_highlight">default=' + commandConfig.options[option].default + '</span>'
                }
                showStr += '<span class="help-span">' + (commandConfig.options[option].hasOwnProperty('desc') ? commandConfig.options[option]['desc'] : '') + '</span>'
                showStr += '</li>'
            }
        }
        if (commandConfig.hasOwnProperty('subCmds')) {
            for (let subcmd in commandConfig.subCmds) {
                showStr += '<li>' + self.printCmdDetail(subcmd, commandConfig.subCmds[subcmd], $.trim(commandLink + ' ' + subcmd)) + '</li>';
            }
        }

        return showStr + '</ul>';
    }

    this.printCmds = function (all_commands) {
        let showStr = "<div class='content-list-1'>";
        for (let cmd in all_commands) {
            showStr += '<a class="command-exec help-span" data-command="' + cmd + ' --help">' + cmd + '</a>';
        }
        showStr += '</div>'
        return showStr;
    }

    this.printCmdAndDesc = function (cmdwin) {
        let showStr = "";
        for (let cmd in cmdwin.all_commands) {
            commandObject = cmdwin.command.getCmdInstance(cmdwin.all_commands[cmd]);
            showStr += '<div><code class="command-exec help-span">' + cmd + '</code> <span class="help-span">' + getPropertyVal(commandObject, 'desc') + '</span></div>';
        }
        return showStr;
    }

    this.Exec = function (command, cmdwin) {
        let showStr = ''
        if (command.content.length > 0) {
            command.content.forEach(cmd => {
                if (cmdwin.all_commands.hasOwnProperty(cmd)) {
                    commandObject = cmdwin.command.getCmdInstance(cmdwin.all_commands[cmd]);
                    // commandObject = new cmdwin.all_commands[cmd]();
                    showStr += this.printCmdDetail('', commandObject, cmd);
                } else {
                    showStr += cmdwin.getErrorOutput('the command "' + cmd + '" is invalid.')
                }
            });
        } else {
            showStr += this.printCmdAndDesc(cmdwin)
        }
        cmdwin.displayOutput(showStr);
    }
}

var timeCmd = function () {
    this.options = {
        timestamp: {
            simple: "t",
            desc: "Get timestamp. useage: <code>time `2020-07-04 14:00:00` -t</code>"
        },
        microtimestamp: {
            simple: "m",
            desc: "Get micro timestamp. useage: <code>time `2020-07-04 14:00:00` -m</code>"
        },
        date: {
            simple: "d",
            desc: "Get date. useage: <code>time `1593842041976` -d</code>"
        }
    };
    this.desc = "Useage: <code>time `2020-07-04 14:00:00` -t</code>"
    this.defaultOption = "date";
    this.Exec = function (command, cmdwin) {
        if (command.options.hasOwnProperty('timestamp')) {
            var timestamp = null;
            if (typeof command.options.timestamp != "boolean") {
                timestamp = new Date(command.options.timestamp).getTime();
            } else if (command.content.length > 0) {
                timestamp = new Date(command.content[0]).getTime();
            } else {
                timestamp = new Date().getTime()
            }
            cmdwin.displayOutput(parseInt(timestamp / 1000) + '');
        }
        if (command.options.hasOwnProperty('microtimestamp')) {
            var microtimestamp = null;
            if (typeof command.options.microtimestamp != "boolean") {
                microtimestamp = new Date(command.options.microtimestamp).getTime();
            } else if (command.content.length > 0) {
                microtimestamp = new Date(command.content[0]).getTime();
            } else {
                microtimestamp = new Date().getTime()
            }
            cmdwin.displayOutput(microtimestamp + '');
        }
        if (command.options.hasOwnProperty('date')) {
            var date = new Date();
            if (typeof command.options.date != "boolean") {
                date = new Date(parseInt(Pad(command.options.date, 13, 1)));
            } else if (command.content.length > 0) {
                date = new Date(parseInt(Pad(command.content[0], 13, 1)));
            }

            cmdwin.displayOutput(formateTime(date));
        }
    }
}

var tabCmd = function () {
    this.desc = "View the tabs opened by the browser.";
    this.subCmds = {
        new: {
            desc: "New tab in the current window",
            Exec: function (command, terminal) {
                api_send_message({
                    type: "browser-tabs",
                    method: 'new-tab',
                });
            }
        },
        newwindow: {
            desc: "New tab in the new window",
            Exec: function (command, terminal) {
                api_send_message({
                    type: "browser-tabs",
                    method: 'new-win-tab',
                });
            }
        },
        copy: {
            desc: "Copy the current tab in the current window",
            Exec: function (command, terminal) {
                api_send_message({
                    type: "browser-tabs",
                    method: 'copy-tab',
                });
            }
        },
        skip: {
            desc: "Skip the current tab in the current window",
            options: {
                step: {
                    simple: "s",
                    desc: "The step of skip in the current window",
                    dataType: "number",
                    default: 1
                }
            },
            Exec: function (command, terminal) {
                api_send_message({
                    type: "browser-tabs",
                    method: 'skip-tab',
                    options: command.options
                });
            }
        },
        close: {
            desc: "close tabs",
            options: {
                others: {
                    simple: "o",
                    desc: "close others in the current window"
                },
                others_all: {
                    simple: "O",
                    desc: "close others of all windows"
                },
                toright: {
                    simple: "r",
                    desc: "close to the right in the current window"
                },
                toright_all: {
                    simple: "R",
                    desc: "close to the right of all windows"
                },
                toleft: {
                    simple: 'l',
                    desc: "close to the left in the current window"
                },
                toleft_all: {
                    simple: 'L',
                    desc: "close to the left of all windows"
                },
                all: {
                    simple: "a",
                    desc: "close all in the current window"
                },
                all_win: {
                    simple: "A",
                    desc: "close all tabs of all windows"
                },
                query: {
                    simple: "q",
                    desc: "close the queried in the current window"
                },
                query_all: {
                    simple: "Q",
                    desc: "close the queried of all windows"
                }
            },
            Exec: function (command, terminal) {
                api_send_message({
                    type: "browser-tabs",
                    method: "close",
                    options: command.options,
                    content: command.content
                });
            }
        },
        capture: {
            desc: "capture visible tab",
            Exec: function (command, terminal) {
                terminal.toggleCmdWin('hide')
                api_send_message({
                    type: "browser-tabs",
                    method: "capture",
                    options: command.options,
                    content: command.content,
                    callback: function (result) {
                        var image = new Image()
                        image.setAttribute('crossOrigin', 'anonymous')
                        image.onload = function () {
                            var canvas = document.createElement('canvas')
                            canvas.width = image.width
                            canvas.height = image.height

                            var context = canvas.getContext('2d')
                            context.drawImage(image, 0, 0, image.width, image.height)
                            var url = canvas.toDataURL('image/png')

                            var a = document.createElement('a')
                            var event = new MouseEvent('click')

                            a.download = document.title || 'download'
                            a.href = url
                            a.dispatchEvent(event)
                        }
                        image.src = result.data
                    }
                });
            }
        },
        'show-recently-closed': {
            desc: "Gets the list of recently closed tabs and/or windows.",
            Exec: function (command, terminal) {
                api_send_message({
                    type: "browser-tabs",
                    method: 'recently-closed',
                    options: command.options,
                    callback: function (msg) {
                        let results = msg.data;
                        let showStr = '';
                        let sessionIdList = [];
                        results.forEach(item => {
                            let tab = item.tab;
                            sessionIdList.push(tab.sessionId)
                            showStr += "<li class='content-list-1' data-type='tabs'><a class='command-exec' data-command='tab restore " + tab.sessionId + "'>#" + tab.sessionId + '# ' + tab.title + "</a></li>";
                        });
                        let showTitleStr = '<div class="content-title-1">Recently Closed (' + results.length + ') <a class="command-exec" data-command="tab restore ' + sessionIdList.join(' ') + '"> RestoreAll</a></div>';
                        terminal.displayOutput(showStr ? showTitleStr + showStr : 'No result.');
                    }
                });
            }
        },
        'restore': {
            desc: "Reopens a windows or tabs. If no specified session, the most recently closed session is restored.",
            Exec: function (command, terminal) {
                api_send_message({
                    type: "browser-tabs",
                    method: 'restore',
                    content: command.content,
                    callback: function (msg) {
                        let results = msg.data;
                        terminal.displayOutput('');
                    }
                });
            }
        }
    };
    this.Exec = function (command, terminal) {
        var query = {};
        if (command.content.length > 0) {
            query.title = '*' + command.content[0] + '*';
        }
        api_send_message({
            type: "browser-tabs",
            method: 'query',
            options: { query: query, type: 'query' },
            callback: function (msg) {
                let results = msg.data;
                let showStr = "";
                let currentWin = 0;
                let winIndex = 0;
                results.forEach(tab => {
                    if (tab.windowId != currentWin) {
                        currentWin = tab.windowId;
                        winIndex++;
                        showStr += '<div class="content-title-1">window ' + winIndex + '</div>';
                    }
                    showStr += "<li class='content-list-1' data-type='tabs'>" + (terminal.is_extension_page ? "<img src='" + api_getIcon(tab.url) + "'> " : "") + "<a data-tab-winid='" + tab.windowId + "' data-tab-id='" + tab.id + "' class='" + (tab.active ? 'active' : '') + "'>" + tab.title + "</a></li>";
                });
                terminal.displayOutput(showStr ? showStr : 'No result.');
                terminal.options.selector.find('.cmd-output [data-type="tabs"] a[data-tab-id]').off('click').on('click', function () {
                    api_send_message({
                        type: "browser-tabs",
                        method: "update",
                        options: { tabId: $(this).data('tab-id'), winId: $(this).data('tab-winid'), updateProperties: { active: true } }
                    })
                })
            }
        });
    }
}
var terminalCmd = function () {
    let self = this
    this.desc = 'Set window properties <code>terminal max</code>',
        this.subCmds = {
            new: {
                desc: 'New terminal tab',
                options: {
                    command: {
                        simple: 'c',
                        desc: 'The command will be run when opened',
                        default: ''
                    }
                },
                Exec: function(command, terminal) {
                    let words = CryptoJS.enc.Utf8.parse(command.options.command);
                    api_send_message({
                        type: 'new-tab',
                        url: chrome.runtime.getURL('main.html?cmd='+CryptoJS.enc.Base64.stringify(words)),
                    });
                }
            },
            max: {
                desc: 'Maximize window',
                Exec: function (command, terminal) {
                    terminal.options.selector.css({ "width": '100%', "height": '100%', "left": "0px", "top": "0px" });
                }
            },
            min: {
                desc: "Minimize window",
                Exec: function (command, terminal) {
                    terminal.options.selector.css({ "width": '60%', "height": '60%', "left": "20%", "top": "20%" });
                }
            },
            close: {
                desc: "Close and clean the window",
                Exec: function (commands, terminal) {
                    terminal.clearScreen();
                    toggleCmdWin('hide');
                }
            },
            hide: {
                desc: "Hide the window",
                Exec: function () {
                    toggleCmdWin('hide');
                }
            },
            scale: {
                desc: "Scaling ratio of the window. <code class='command-exec' data-command='terminal scale 1.5'>terminal scale 1.5</code>",
                Exec: function (command, terminal) {
                    let rate = command.content.length > 0 ? parseFloat(command.content[0]) : 1;
                    rate = rate <= 0 ? 1 : rate;
                    let minWidth = 300;
                    let minHeight = 250;
                    let winWidth = $(window).width();
                    let winHeight = $(window).height();
                    let width = terminal.options.selector.width();
                    let height = terminal.options.selector.height();
                    newWidth = width * rate > winWidth ? winWidth : width * rate;
                    newHeight = height * rate > winHeight ? winHeight : height * rate;
                    newWidth = newWidth < minWidth ? minWidth : newWidth;
                    newHeight = newHeight < minHeight ? minHeight : newHeight;
                    let position = terminal.options.selector.position();
                    let left = position.left - (newWidth - width) / 2;
                    let top = position.top - (newHeight - height) / 2;
                    terminal.options.selector.css({
                        "width": newWidth + 'px',
                        "height": newHeight + 'px',
                        "left": (left > 0 ? left : 0) + "px",
                        "top": (top > 0 ? top : 0) + "px"
                    });
                }
            },
            position: {
                desc: "Set the position of the window. <code>terminal position -l 0 -t 0</code> The window is near the upper left corner.",
                options: {
                    left: {
                        simple: 'l',
                        desc: 'Distance to the left',
                    },
                    increase_left: {
                        simple: 'L',
                        desc: 'Increase the Distance to the left',
                    },
                    top: {
                        simple: 't',
                        desc: 'Distance to the top',
                    },
                    increase_top: {
                        simple: 'T',
                        desc: 'Increase the Distance to the top',
                    },
                    right: {
                        simple: 'r',
                        desc: 'Distance to the right',
                    },
                    increase_right: {
                        simple: 'R',
                        desc: 'Increase the Distance to the right',
                    },
                    bottom: {
                        simple: 'b',
                        desc: 'Distance to the bottom',
                    },
                    increase_bottom: {
                        simple: 'B',
                        desc: 'Increase the Distance to the bottom',
                    },
                    width: {
                        simple: 'w',
                        desc: 'The width of the window',
                    },
                    increase_width: {
                        simple: 'W',
                        desc: 'Increase the The width of the window',
                    },
                    height: {
                        simple: 'h',
                        desc: 'The height of the window',
                    },
                    increase_height: {
                        simple: 'H',
                        desc: 'Increase the The width of the window',
                    },
                },
                Exec: function (commands, terminal) {
                    let position = terminal.options.selector.position();
                    if (commands.options.hasOwnProperty('left')) {
                        terminal.options.selector.css('left', commands.options['left']);
                    }
                    if (commands.options.hasOwnProperty('increase_left')) {
                        terminal.options.selector.css('left', position.left + parseFloat(commands.options['increase_left']));
                    }
                    if (commands.options.hasOwnProperty('top')) {
                        terminal.options.selector.css('top', commands.options['top']);
                    }
                    if (commands.options.hasOwnProperty('increase_top')) {
                        terminal.options.selector.css('top', position.top + parseFloat(commands.options['increase_top']));
                    }
                    if (commands.options.hasOwnProperty('bottom')) {
                        terminal.options.selector.css('top', $(window).height() - commands.options['bottom'] - terminal.options.selector.height());
                    }
                    if (commands.options.hasOwnProperty('increase_bottom')) {
                        let top
                        terminal.options.selector.css('top', position.top - commands.options['increase_bottom']);
                    }
                    if (commands.options.hasOwnProperty('right')) {
                        terminal.options.selector.css('left', $(window).width() - terminal.options.selector.width() - commands.options['right']);
                    }
                    if (commands.options.hasOwnProperty('increase_right')) {
                        terminal.options.selector.css('left', position.left - commands.options['increase_right']);
                    }
                    if (commands.options.hasOwnProperty('width')) {
                        terminal.options.selector.css('width', commands.options['width']);
                    }
                    if (commands.options.hasOwnProperty('increase_width')) {
                        terminal.options.selector.css('width', terminal.options.selector.width() + parseFloat(commands.options['increase_width']));
                    }
                    if (commands.options.hasOwnProperty('height')) {
                        terminal.options.selector.css('height', commands.options['height']);
                    }
                    if (commands.options.hasOwnProperty('increase_height')) {
                        terminal.options.selector.css('height', terminal.options.selector.height() + parseFloat(commands.options['increase_height']));
                    }
                }
            },
        }
}
var syscmds = {
    "help": {
        "version": "1.0.0",
        "site": "*",
        "index_func": helpCmd
    },
    "ls": {
        "version": "1.0.0",
        "site": "*",
        "index_func": lsCmd
    },
    // "cd": {
    //     "version": "1.0.0",
    //     "site": "*",
    //     "index_func": cdCmd
    // },
    // "cmd": {
    //     "version": "1.0.0",
    //     "site": "*",
    //     "index_func": cmdCmd
    // },
    "cron": {
        "version": "1.0.0",
        "site": "*",
        "index_func": cronCmd
    },
    "time": {
        "version": "1.0.0",
        "site": "*",
        "index_func": timeCmd
    },
    "clear": {
        "version": "1.0.0",
        "site": "*",
        "index_func": clearCmd
    },
    "terminal": {
        "version": "1.0.0",
        "site": "*",
        "index_func": terminalCmd
    },
    "storage": {
        "version": "1.0.0",
        "site": "*",
        "index_func": storageCmd
    },
    "tab": {
        "version": "1.0.0",
        "site": "*",
        "index_func": tabCmd
    },
    "bookmark": {
        "version": "1.0.0",
        "site": "*",
        "index_func": bookmarkCmd
    },
    "history": {
        "version": "1.0.0",
        "site": "*",
        "index_func": historyCmd
    },

    "curl": {
        "version": "1.0.0",
        "site": "*",
        "index_func": curlCmd
    },
    "search": {
        "version": "1.0.0",
        "site": "*",
        "index_func": searchCmd
    },
    "translate": {
        "version": "1.0.0",
        "site": "*",
        "index_func": translateCmd
    },
    "encode": {
        "version": "1.0.0",
        "site": "*",
        "index_func": encodeCmd
    },
    "decode": {
        "version": "1.0.0",
        "site": "*",
        "index_func": decodeCmd
    },
    "json": {
        "version": "1.0.0",
        "site": "*",
        "index_func": jsonCmd
    },
    "str": {
        "version": "1.0.0",
        "site": "*",
        "index_func": strCmd
    },
    "github": {
        "version": "1.0.0",
        "site": "*",
        "index_func": githubCmd
    },
    "browser": {
        "version": "1.0.0",
        "site": "*",
        "index_func": browserCmd
    },
    "rule": {
        "version": "1.0.0",
        "site": "*",
        "index_func": ruleCmd
    },
    "cookie": {
        "version": "1.0.0",
        "site": "*",
        "index_func": cookieCmd
    }
}
var EditorWin = function(user_config) {
    this.saveCallback = null;
    this.quitCallback = null;
    this.options = {
        selector: null,
        terminal: null,
    }
    $.extend(this.options, user_config);
    this.setDom();
}

EditorWin.prototype.setDom = function() {
    this.wrapper = $(this.options.selector);
    this.container = $('<textarea/>')
    .addClass('editor-content cmd-interface')
    .appendTo(this.wrapper);

    this.bar = $('<div/>').addClass('editor-opt')
    .attr({"contenteditable": true, "placeholder": '"w"-save, "q"-quit. "wq"-save and quit, "esc"-focus tool bar'})
    .appendTo(this.wrapper);

    this.container.keydown($.proxy(this.handleContainerKeyDown, this));
    this.bar.keypress($.proxy(this.handleBarKeyPress, this));
}

EditorWin.prototype.handleContainerKeyDown = function (e) {
    let keyCode = e.keyCode || e.which;
    if (keyCode == 27) {
        this.barFocus('')
        e.preventDefault();
    } else if (keyCode == 9) {
        this.editorTabKeyHandle(e)
    }
}

EditorWin.prototype.handleBarKeyPress = function (e) {
    let keyCode = e.keyCode || e.which;
    if (keyCode == 13) {
        e.preventDefault();
        this.bar.hide();
        let ins = this.bar.text().toLowerCase();
        if (['q', 'wq', 'w', ':q', ':w', ':wq'].indexOf(ins) > -1) {
          if (ins.indexOf('w') > -1) {
            this.saveCallback && this.saveCallback(this.container.val())
          }
          if (ins.indexOf('q') > -1) {
            this.hideEditor();
            this.options.terminal.focusOnInput()
            this.quitCallback && this.quitCallback();
            return
          }
        }
        this.editorFocus();
    }
}

EditorWin.prototype.editorFocus = function () {
    this.container.focus();
}

EditorWin.prototype.barFocus = function(content) {
    this.bar.text(content).show().focus();
}

EditorWin.prototype.editorTabKeyHandle = function (e) {
    e.preventDefault();

    let container = this.container[0]

    container.setRangeText(
        '  ',
        container.selectionStart,
        container.selectionStart,
        'end'
    )
}

EditorWin.prototype.showEditor = function (content, saveCallback, quitCallback) {
    this.options.terminal.hide();
    this.wrapper.show();

    if (content !== null) this.container.val(content);
    this.barFocus('');
    this.saveCallback = saveCallback;
    this.quitCallback = quitCallback;
}

EditorWin.prototype.hideEditor = function () {
    this.options.terminal.show();
    this.wrapper.hide();
}
// const { trim } = require("jquery");
if (!String.prototype.startsWith) {
  String.prototype.startsWith = function (searchString, position) {
    position = position || 0;
    return this.indexOf(searchString, position) === position;
  };
}

//jQuery实现textarea高度根据内容自适应
$.fn.extend({
  txtaAutoHeight: function () {
    return this.each(function () {
      var $this = $(this);
      if (!$this.attr('initAttrH')) {
        $this.attr('initAttrH', $this.outerHeight());
      }
      setAutoHeight(this).on('input', function () {
        setAutoHeight(this);
      });
      setAutoHeight(this).on('focus', function () {
        setAutoHeight(this);
      });
    });
    function setAutoHeight(elem) {
      var $obj = $(elem);
      return $obj.css({ height: $obj.attr('initAttrH'), 'overflow-y': 'hidden' }).height(elem.scrollHeight);
    }
  }
})

/**
 * Stack for holding previous commands for retrieval with the up arrow. 
 * Stores data in localStorage. Won't push consecutive duplicates.
 *
 * @author   Jake Gully, chimpytk@gmail.com
 * @license  MIT License
 */

/**
 * Constructor
 * @param {string}  id       Unique id for this stack
 * @param {integer} max_size Number of commands to store
 */
function CmdStack(id, max_size) {
  "use strict";

  var instance_id = id,
    cur = 0,
    arr = []; // This is a fairly meaningless name but
  // makes it sound like this function was
  // written by a pirate.  I'm keeping it.

  if (typeof id !== 'string') {
    throw 'Stack error: id should be a string.';
  }

  if (typeof max_size !== 'number') {
    throw 'Stack error: max_size should be a number.';
  }

  function getCmdStackKey() {
    return "cmd_stack_" + instance_id;
  }

  /**
   * Store the array in localstorage
   */
  function setArray(stackList) {
    // localStorage['cmd_stack_' + instance_id] = JSON.stringify(arr);
    let data = {};
    data[getCmdStackKey()] = stackList;
    api_storage_sync_set(data);
  }

  /**
   * Load array from localstorage
   */
  function getArray(callback) {
    api_storage_sync_get(getCmdStackKey(), function (result) {
      if (result.hasOwnProperty(getCmdStackKey())) {
        callback && callback(result[getCmdStackKey()]);
      } else {
        callback && callback([]);
      }
    });
  }

  /**
   * Push a command to the array
   * @param  {string} cmd Command to append to stack
   */
  function push(cmd) {
    getArray(items => {
      arr = items;
      // don't push if same as last command
      if (cmd === arr[arr.length - 1]) {
        return false;
      }
      arr.push(cmd);

      // crop off excess
      while (arr.length > max_size) {
        arr.shift();
      }

      cur = arr.length;
      setArray(arr);
    });
  }

  /**
   * Get previous command from stack (up key)
   * @return {string} Retrieved command string
   */
  function prev() {
    cur -= 1;

    if (cur < 0) {
      cur = 0;
    }

    return arr[cur];
  }

  /**
   * Get next command from stack (down key)
   * @return {string} Retrieved command string
   */
  function next() {
    cur = cur + 1;

    // Return a blank string as last item
    if (cur === arr.length) {
      return "";
    }

    // Limit
    if (cur > (arr.length - 1)) {
      cur = (arr.length - 1);
    }

    return arr[cur];
  }

  /**
   * Move cursor to last element
   */
  function reset() {
    getArray(items => {
      arr = items;
      cur = arr.length;
    });
  }

  /**
   * Empty array and remove from localstorage
   */
  function empty() {
    arr = undefined;
    localStorage.clear();
    api_storage_sync_remove(getCmdStackKey());
    reset();
  }

  /**
   * Get current cursor location
   * @return {integer} Current cursor index
   */
  function getCur() {
    return cur;
  }

  /**
   * Get entire stack array
   * @return {array} The stack array
   */
  function getArr() {
    return arr;
  }

  /**
   * Get size of the stack
   * @return {Integer} Size of stack
   */
  function getSize() {
    return arr.length;
  }

  return {
    push: push,
    prev: prev,
    next: next,
    reset: reset,
    empty: empty,
    getCur: getCur,
    getArr: getArr,
    getSize: getSize
  };
}

function isURL(str) {
  var pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
    '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
    '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
    '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
    '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
    '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator
  return pattern.test(str);
}

function htmlEncode(str) {
  var s = "";
  if (str.length === 0) {
    return "";
  }
  s = str.replace(/&/g, "&amp;");
  s = s.replace(/</g, "&lt;");
  s = s.replace(/>/g, "&gt;");
  s = s.replace(/ /g, "&nbsp;");
  s = s.replace(/\'/g, "&#39;");//IE下不支持实体名称
  s = s.replace(/\"/g, "&quot;");
  return s;
}

/**
 * HTML5 Command Line Terminal
 */
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(factory);
  } else if (typeof exports === 'object') {
    module.exports = factory();
  } else {
    root.TerminalWin = factory();
  }
}(this, function () {
  "use strict";
  var TerminalWin = function (user_config) {
    this.keys_array = [9, 13, 38, 40, 27],
    this.timeout = 30;
    this.style = 'dark',
    this.output_init = '<code><div class="content-list-1">' + api_locales('terminal_desc', api_locales('version')) + '</div><hr style="border-block-color:#fff;margin-bottom:5px;" /></code>',
    this.popup = true,
    this.prompt_str = '$ ',
    this.autofill = '',
    this.tab_nums = 0,
    this.speech_synth_support = ('speechSynthesis' in window && typeof SpeechSynthesisUtterance !== 'undefined'),
    this.options = {
      busy_text: 'Communicating...',
      external_processor: function () { },
      filedrop_enabled: false,
      // file_upload_url:     'ajax/uploadfile.php',
      history_id: 'cmd_history',
      selector: '#cmd',
      source: 'self',
      typewriter_time: 32,
      init_cmd: ''
    },
    this.voices = false;
    this.customcmds = {};
    this.thirdcmds = {};
    this.user_data = {};
    this.tmp_data = {};
    this.all_commands = syscmds;
    this.syscmds = Object.keys(this.all_commands);
    this.initCmd(this.all_commands);

    $.extend(this.options, user_config);

    this.is_extension_page = this.options.source == 'self' ? true : false;

    var self = this;
    api_send_message({
      type: 'cmdhub',
      method: 'get_cmdhub',
      callback: function (res) {
        if (typeof res == 'object') {
          $.extend(self.thirdcmds, res.meta.data);
          self.initCmd(self.thirdcmds);
          for (let cmd in self.thirdcmds) {
            self.all_commands[cmd] = self.thirdcmds[cmd];
          }
        }
      }
    });

    api_send_message({
      space: "user",
      type: "storage",
      method: 'get',
      options: {area: "remote"},
      content: ['data'],
      callback: function (msg) {
          if (typeof msg.meta.data.data != 'undefined') {
            self.user_data = msg.meta.data.data
          }
      }
    });

    api_send_message({
      type: 'cmdhub',
      method: 'get_custom_cmdhub',
      callback: function (res) {
        if (typeof res == 'object') {
          $.extend(self.customcmds, res.meta.data);
          self.initCmd(self.customcmds);
          for (let cmd in self.customcmds) {
            self.all_commands[cmd] = self.customcmds[cmd];
          }
        }
      }
    });

    if (!$(this.options.selector).length) {
      throw 'Cmd err: Invalid selector.';
    }

    this.cmd_stack = new CmdStack(this.options.history_id, 30);

    this.cmd_stack.reset();
    this.setupDOM();
    this.input.focus();

    this.initStartShows()

  }

  TerminalWin.prototype.initStartShows = function() {
    let self = this
    if (this.options.init_cmd) {
      setTimeout(()=>{this.handleInput(this.options.init_cmd)}, 500);
    }
    api_send_message({
      type: 'manifest',
      callback: function (result) {
        let info = result && result.hasOwnProperty('data') ? result.data : [];
        let lang = navigator.language == 'zh-CN' ? '中文' : 'English'
        self.displayOutput(`Welcome to <a href="https://github.com/web-terminal/web-terminal/wiki/${lang}" target="_blank">Web-Terminal</a> ${info.version}`)
      }
    });

  }

  TerminalWin.prototype.setUserData = function(space, data, callback) {
    var self = this;
    this.user_data[space] = data
    api_send_message({
      space: "user",
      type: "storage",
      method: 'set',
      options: {area: "remote"},
      content: ['data', self.user_data],
      callback: callback
    });
  }

  TerminalWin.prototype.getUserData = function(space) {
    return typeof this.user_data[space] == 'undefined' ? {} : this.user_data[space]
  }

  TerminalWin.prototype.initCmd = function (cmdConfigs) {
    for (let cmd in cmdConfigs) {
      Command.InitExec(this, cmd, cmdConfigs[cmd]);
    }
  }

  TerminalWin.prototype.toggleCmdWin = function (show) {
    toggleCmdWin && toggleCmdWin(show);
  }

  TerminalWin.prototype.show = function () {
    this.options.selector.show();
  }

  TerminalWin.prototype.hide = function () {
    this.options.selector.hide();
  }

  // ====== Layout / IO / Alter Interface =========

  /**
   * Create DOM elements, add click & key handlers
   */
  TerminalWin.prototype.setupDOM = function () {
    this.wrapper = $(this.options.selector).addClass('cmd-interface');

    this.container = $('<div/>')
      .addClass('cmd-container')
      .appendTo(this.wrapper);

    if (this.options.filedrop_enabled) {
      this.setupFiledrop(''); // adds dropzone div
    }

    this.clearScreen(); // adds output, input and prompt

    $(this.options.selector).on('click', $.proxy(this.focusOnInput, this));
    $(window).resize($.proxy(this.resizeInput, this));

    this.wrapper.keydown($.proxy(this.handleKeyDown, this));
    this.wrapper.keyup($.proxy(this.handleKeyUp, this));
    this.wrapper.keypress($.proxy(this.handleKeyPress, this));

    let self = this;
    this.wrapper.find('.cmd-output').on('click', function (e) {
      e.stopPropagation();
      let selectionTxt = window.getSelection().toString();
      if (!selectionTxt) {
        if (self.input.attr('disabled') == 'disabled') {
          self.enableInput();
        }
        self.focusOnInput();
      }
    });
  }

  /**
   * Changes the input type
   */
  TerminalWin.prototype.showInputType = function (input_type) {
    switch (input_type) {
      case 'password':
        this.input = $('<input/>')
          .attr('type', 'password')
          .attr('maxlength', 512)
          .addClass('cmd-in');
        break;
      case 'input':
        this.input = $('<input/>')
          .attr('type', 'text')
          .attr('maxlength', 512)
          .addClass('cmd-in');
      case 'textarea':
        this.input = $('<textarea/>')
          .addClass('cmd-in');
      default:
        this.input = $('<textarea/>')
          .addClass('cmd-in')
        this.input.off("input");
        this.input.off("focus");
        break;
    }

    this.container.children('.cmd-in').remove();

    this.input.appendTo(this.container).attr('title', 'Terminal input');
    this.input.txtaAutoHeight();


    this.resizeInput();
    // this.focusOnInput();
  }

  TerminalWin.prototype.getErrorOutput = function (cmd_out) {
    return '<span class="red_highlight"> Error: ' + cmd_out + '</span>';
  }

  TerminalWin.prototype.displayErrorOutput = function (cmd_out) {
    this.displayOutput(this.getErrorOutput(cmd_out))
  }

  /**
   * Takes the client's input and the server's output
   * and displays them appropriately.
   *
   * @param   string  cmd_in      The command as entered by the user
   * @param   string  cmd_out     The server output to write to screen
   */
  TerminalWin.prototype.displayOutput = function (cmd_out, encode) {
    switch (typeof cmd_out) {
      case 'object':
        cmd_out = JSON.stringify(cmd_out);
        break;
      case 'string':
      case 'number':
      case 'boolean':
        break;
      default:
        cmd_out = this.getErrorOutput('invalid cmd_out returned. typeof is ' + (typeof cmd_out));
    }

    if (encode) {
      cmd_out = htmlEncode(cmd_out)
    }
    cmd_out && (this.cmd_output = cmd_out);
    cmd_out && this.output.append('<code>' + cmd_out + '</code>');

    this.input.val(this.autofill).removeAttr('disabled');

    this.enableInput();
    this.focusOnInput();
  }

  /**
   * Take an input string and output it to the screen
   */
  TerminalWin.prototype.displayInput = function (cmd_in) {
    cmd_in = cmd_in.replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");

    this.output.append('<div><span class="prompt">' + this.prompt_str + '</span> ' +
      '<span class="grey_text">' + cmd_in + '</span></div>');
  }

  /**
   * Set the prompt string
   * @param {string} new_prompt The new prompt string
   */
  TerminalWin.prototype.setPrompt = function (new_prompt) {
    if (typeof new_prompt !== 'string') {
      throw 'Cmd error: invalid prompt string.';
    }

    this.prompt_str = new_prompt;
    this.prompt_elem.html(this.prompt_str);
  }

  TerminalWin.prototype.validator = function (command_name) {
    if (this.all_commands.hasOwnProperty(command_name)) {
      if (location.href.startsWith('chrome-extension://')) {
        if (typeof this.all_commands[command_name] == 'string' || command_name == 'js') {
          throw "You can only exec this command at the normal url page, can not exec js at the extension pages.";
        }
      }
    } else {
      throw "Unrecognised command <code>" + command_name + "</code>";
    }
  }

  /**
   * Post-file-drop dropzone reset
   */
  TerminalWin.prototype.resetDropzone = function () {
    dropzone.css('display', 'none');
  }

  /**
   * Add file drop handlers
   */
  TerminalWin.prototype.setupFiledrop = function (wrapper) {
    this.dropzone = $('<div/>')
      .addClass('dropzone')
      .appendTo(wrapper)
      .filedrop({
        url: this.options.file_upload_url,
        paramname: 'dropfile', // POST parameter name used on serverside to reference file
        maxfiles: 10,
        maxfilesize: 2, // MBs
        error: function (err, file) {
          switch (err) {
            case 'BrowserNotSupported':
              alert('Your browser does not support html5 drag and drop.');
              break;
            case 'TooManyFiles':
              this.displayInput('[File Upload]');
              this.displayOutput('Too many files!');
              this.resetDropzone();
              break;
            case 'FileTooLarge':
              // FileTooLarge also has access to the file which was too large
              // use file.name to reference the filename of the culprit file
              this.displayInput('[File Upload]');
              this.displayOutput('File too big!');
              this.resetDropzone();
              break;
            default:
              this.displayInput('[File Upload]');
              this.displayOutput('Fail D:');
              this.resetDropzone();
              break;
          }
        },
        dragOver: function () { // user dragging files over #dropzone
          this.dropzone.css('display', 'block');
        },
        dragLeave: function () { // user dragging files out of #dropzone
          this.resetDropzone();
        },
        docOver: function () { // user dragging files anywhere inside the browser document window
          this.dropzone.css('display', 'block');
        },
        docLeave: function () { // user dragging files out of the browser document window
          this.resetDropzone();
        },
        drop: function () { // user drops file
          this.dropzone.append('<br>File dropped.');
        },
        uploadStarted: function (i, file, len) {
          this.dropzone.append('<br>Upload started...');
          // a file began uploading
          // i = index => 0, 1, 2, 3, 4 etc
          // file is the actual file of the index
          // len = total files user dropped
        },
        uploadFinished: function (i, file, response, time) {
          // response is the data you got back from server in JSON format.
          if (response.error !== '') {
            upload_error = response.error;
          }
          this.dropzone.append('<br>Upload finished! ' + response.result);
        },
        progressUpdated: function (i, file, progress) {
          // this function is used for large files and updates intermittently
          // progress is the integer value of file being uploaded percentage to completion
          this.dropzone.append('<br>File uploading...');
        },
        speedUpdated: function (i, file, speed) { // speed in kb/s
          this.dropzone.append('<br>Upload speed: ' + speed);
        },
        afterAll: function () {
          // runs after all files have been uploaded or otherwise dealt with
          if (upload_error !== '') {
            this.displayInput('[File Upload]');
            this.displayOutput('Error: ' + upload_error);
          } else {
            this.displayInput('[File Upload]');
            this.displayOutput('[File Upload]', 'Success!');
          }

          upload_error = '';

          this.dropzone.css('display', 'none');
          this.resetDropzone();
        }
      });
  }

  /**
   * [invert description]
   * @return {[type]} [description]
   */
  TerminalWin.prototype.invert = function () {
    this.wrapper.toggleClass('inverted');
  }



  // ====== Handlers ==============================

  /**
   * Do something
   */
  TerminalWin.prototype.handleInput = function (input_str, from_remote) {
    input_str = $.trim(input_str);
    var self = this;
    if (input_str) {
      try {
        self.pipelineSeriesCommands(self.splitCommands(input_str), from_remote);
      } catch (err) {
        self.displayOutput(err);
      }
    } else {
      self.displayInput('');
      self.displayOutput('');
    }
  }

  TerminalWin.prototype.pipelineSeriesCommands = function (command_list, from_remote) {
    var self = this;
    var serialExec = function (output, i) {
      if (i < command_list.length) {
        new Promise(function (resolve, reject) {
          try {
            self.feed_data = window.btoa(encodeURIComponent(output));
            // 解析参数
            var parse_data = self.parseParameter(output);
            if (parse_data && parse_data.hasOwnProperty('_error')) {
              self.displayOutput('Pipeline break: ' + parse_data._error);
              return;
            }
            // 对参数进行替换
            command_list[i] = self.replaceParameterPlaceholder(command_list[i], parse_data);
            self.ExecCommands(command_list[i], from_remote);
            // 等待命令返回结果
            var times = 0;
            var intervalHandle = setInterval(function () {
              times++;
              // console.log("set interval: ================");
              if (self.cmd_output != -1 || times / 10 >= self.timeout) {
                // console.log("clearInterval: ================", self.cmd_output);
                clearInterval(intervalHandle);
                resolve(self.cmd_output);
              }
            }, 100);
          } catch (e) {
            reject(e)
          }
        }).then(function (result) {
          serialExec(result, ++i);
        }, function (error) {
          self.displayOutput(error)
        });
      }
    }

    var i = 0;
    serialExec(null, i);
  }

  TerminalWin.prototype.parseParameter = function (params) {
    var parse_data = null;
    try {
      parse_data = JSON.parse(params);
    } catch (e) {
      parse_data = params;
    }
    return parse_data;
  }

  TerminalWin.prototype.replaceParameterPlaceholder = function (command, parse_data) {
    if (!parse_data) return command;

    if (typeof parse_data == "object") {
      for (let i in parse_data) {
        if (typeof i == "string" && command.indexOf("{{" + i + "}}") > -1) {
          command = command.replace("{{" + i + "}}", parse_data[i]);
        }
      }
    }
    return command;
  }

  TerminalWin.prototype.pipelineParallelCommands = function (command_list, from_remote) {
    for (var i in command_list) {
      this.ExecCommands(command_list[i], from_remote);
    }
  }

  TerminalWin.prototype.ExecCommands = function (command_line, from_remote) {
    var self = this;
    var command = new Command(command_line, self.prompt_str);
    self.cmd_output = -1;
    self.command = command;
    command.Exec(self, from_remote, function (result) {
      self.displayOutput(self.formateOutput(result, from_remote));
    });
  }

  TerminalWin.prototype.splitCommands = function (input_str) {
    let command_list = [];
    let quote = "";
    if (typeof input_str == 'object' && input_str.length > 0) {
      for (let i in input_str) {
        command_list = command_list.concat(this.splitCommands(input_str[i]));
      }
      return command_list;
    }
    for (let i = 0; i < input_str.length; i++) {
      let chr = input_str.charAt(i);
      if (chr == '"' || chr == "'" || chr == "`") {
        // 如果是开头，则开始对字符串进行包裹
        if (!quote) {
          quote = chr;
        } else if (quote && quote == chr && input_str.charCodeAt(i - 1) != 92) {  // 如果是结尾
          quote = "";
        }
      } else if (!quote && chr == '|') {
        command_list.push(input_str.substr(0, i));
        return command_list.concat(this.splitCommands(input_str.substring(i + 1)));
      }
    }
    if (input_str.trim()) {
      command_list.push(input_str);
    }
    // add default command 
    if (command_list.length > 0) {
      var firstCmd = $.trim(command_list[0]).split(" ")[0];
      if (!this.all_commands.hasOwnProperty($.trim(firstCmd)) && this.prompt_str.trim() == '$') {
        // if (command_list[0].startsWith('`') && command_list[0].startsWith('`', command_list[0].length - 1)) {
        //   command_list[0] = 'js ' + command_list[0];
        // } else {
        //   command_list[0] = 'js `' + command_list[0] + '`';
        // }
      }
    }
    return command_list;
  }

  TerminalWin.prototype.formateOutput = function (result, from_remote) {
    var self = this;
    let output = ''
    if (result && result.hasOwnProperty('type')) {
      let data = result.hasOwnProperty('data') ? result.data : [];
      switch (result.type) {
        case 'data-list':
          if (data && typeof data == 'object') {
            for (let i in data) {
              let item = data[i];
              let img = '';
              if (item.hasOwnProperty('icon')) {
                if (item.hasOwnProperty('icon_type') == 'svg') {
                  img = item.icon;
                } else {
                  img = '<img style="max-width:12px;max-height:12px;margin:-1px 10px;" src="' + item.icon + '"/>';
                }
              }
              if (!item.hasOwnProperty('url')) {
                output += '<div><label>' + img + item.title + '</label></div>';
              } else {
                output += '<div><a href="' + item.url + '" target="_blank">' + img + item.title + '</a></div>';
              }
            }
          }
          break;
        case 'html-text':
          output = result.data;
          break;
        case 'command-list':
          if (!from_remote && data && typeof data == 'object' && data.length > 0) {
            self.pipelineSeriesCommands(self.splitCommands(data), from_remote);
          }
          return output;
      }
    } else if (result) {
      output = result;
    }
    return output
  }

  /**
   * Handle JSON responses. Used as callback by external command handler
   * @param  {object} res Chimpcom command object
   */
  TerminalWin.prototype.handleResponse = function (res) {
    if (res.redirect !== undefined) {
      document.location.href = res.redirect;
    }

    if (res.openWindow !== undefined) {
      window.open(res.openWindow, '_blank', res.openWindowSpecs);
    }

    if (res.log !== undefined && res.log !== '') {
      console.log(res.log);
    }

    if (res.show_pass === true) {
      this.showInputType('password');
    } else {
      this.showInputType();
    }

    this.displayOutput(res.cmd_out);

    if (res.cmd_fill !== '') {
      this.wrapper.children('.cmd-container').children('.cmd-in').first().val(res.cmd_fill);
    }
  }

  /**
   * Handle keypresses
   */
  TerminalWin.prototype.handleKeyDown = function (e) {
    var self = this;
    var keyCode = e.keyCode || e.which,
      input_str = this.input.val();
    e.stopPropagation();
    if ($.inArray(keyCode, this.keys_array) > -1) {
      if (keyCode == 27) self.toggleCmdWin('hide');
      e.preventDefault();
    }

    if (keyCode === 9) { //tab
      this.tabComplete(input_str);
    } else if (e.ctrlKey && keyCode == 67) {
      this.displayInput(input_str + "^C");
      this.input.val("");
      return false;
    } else {
      if (keyCode === 13) { // enter
        this.autofill = '';
        if (input_str.charCodeAt(input_str.length - 1) === 92) {
          input_str = input_str.substr(0, input_str.length - 1) + "\n";
          this.input.val(input_str);
          this.focusOnInput();
        } else {
          if (this.input.attr('disabled')) {
            return false;
          }

          if (e.ctrlKey) {
            if (isURL(input_str)) {
              this.goToURL(input_str);
            } else {
              this.handleInput("search " + input_str);
            }
          } else {
            this.disableInput();
            this.handleInput(input_str);
          }

          // push command to stack if using text input, i.e. no passwords
          if (this.input.get(0).type !== 'password' && input_str) {
            this.cmd_stack.push(input_str);
            setTimeout(function () {
              self.cmd_stack.reset();
            }, 200);
          }
        }

      } else if (keyCode === 38) { // up arrow
        // if (input_str !== "" && this.cmd_stack.getCur() === this.cmd_stack.getSize()) {
        //   this.cmd_stack.push(input_str);
        // }

        this.input.val(this.cmd_stack.prev());
        this.resizeInput();
      } else if (keyCode === 40) { // down arrow
        this.input.val(this.cmd_stack.next());
        this.resizeInput();
      } else if (keyCode === 27) { // esc
        // this.options.selector.toggle();
      }
    }
  }

  /**
   * Prevent default action of special keys
   */
  TerminalWin.prototype.handleKeyUp = function (e) {
    var key = e.keyCode || e.which;
    if (key != 91 && key != 17) {
      e.stopPropagation();
    }

    if ($.inArray(key, this.keys_array) > -1) {
      e.preventDefault();
      return false;
    }
    return true;
  }

  /**
   * Prevent default action of special keys
   */
  TerminalWin.prototype.handleKeyPress = function (e) {
    var key = e.keyCode || e.which;
    e.stopPropagation();
    // if ($.inArray(key, this.keys_array) > -1) {
    //   e.preventDefault();
    //   return false;
    // }
    return true;
  }

  /**
   * Complete command names when tab is pressed
   */
  TerminalWin.prototype.tabComplete = function (input_str, from_remote) {
    let self = this;
    self.tab_nums++;
    setTimeout(function () {
      self.tab_nums = 0;
    }, 300);

    var command = new Command(input_str, this.prompt_str);
    var message = {};

    if (self.tab_nums == 1) {
      command.TabCompleteSystem(self);
    } else if (self.tab_nums == 2) {
      message = command.TabComplete(self, from_remote);
      if (message !== 'TabCompleteSystem') {
        if (!from_remote) {
          self.displayOutput(self.formateOutput(message, from_remote));
        }
        self.input.val(input_str);
      }
    }
    return message;
  }


  // ====== Helpers ===============================

  /**
   * Takes a user to a given url. Adds "http://" if necessary.
   */
  TerminalWin.prototype.goToURL = function (url) {
    if (!isURL(url)) {
      // this.displayOutput("please type valid url address.");
      // return
    }
    if (url.substr(0, 4) !== 'http' && url.substr(0, 2) !== '//') {
      url = 'http://' + url;
    }

    if (this.popup) {
      window.open(url, '_blank');
      window.focus();
    } else {
      // break out of iframe - used by chrome plugin
      if (top.location !== location) {
        top.location.href = document.location.href;
      }

      location.href = url;
    }
  }

  /**
   * Give focus to the command input and
   * scroll to the bottom of the page
   */
  TerminalWin.prototype.focusOnInput = function () {
    $(this.options.selector).scrollTop($(this.options.selector)[0].scrollHeight);
    this.input.focus();
  }

  /**
   * Make prompt and input fit on one line
   */
  TerminalWin.prototype.resizeInput = function () {
    var cmd_width = this.wrapper.width() - this.wrapper.find('.main-prompt').first().width() - 25 - 10;
    this.input.css('width', cmd_width);
    this.focusOnInput();
  }

  /**
   * Clear the screen
   */
  TerminalWin.prototype.clearScreen = function () {
    this.container.empty();

    this.output = $('<div/>')
      .addClass('cmd-output')
      .append(this.output_init)
      .appendTo(this.container);

    this.prompt_elem = $('<span/>')
      .addClass('main-prompt')
      .addClass('prompt')
      .html(this.prompt_str)
      .appendTo(this.container);

    this.showInputType();

    this.input.val(this.autofill);

    var self = this;
    this.wrapper.find('.cmd-output').on('click', 'code.command-exec', function () {
      let command = $(this).data('command') ? $(this).data('command') : $(this).text();
      if ($(this).data('type') == 'base64') {
        command = window.atob(command)
      }
      self.input.val(command);
    })
    this.wrapper.find('.cmd-output').on('click', '[toggle-group]', function (event) {
      event.stopPropagation()
      let attrVal = $(this).attr('toggle-group')
      if (!$(this).hasClass('active')) {
        $(this).addClass('active')
        $(this).parents('code').find('[toggle-'+attrVal+']').removeClass('hide');
      } else {
        $(this).removeClass('active')
        $(this).parents('code').find('[toggle-'+attrVal+']').addClass('hide');
      }
    })
    this.wrapper.find('.cmd-output').on('click', 'a.command-exec', function () {
      let command = $(this).data('command') ? $(this).data('command') : $(this).text();
      if ($(this).data('type') == 'base64') {
        command = window.atob(command)
      }
      self.handleInput(command);
    })
  }

  /**
   * Temporarily disable input while runnign commands
   */
  TerminalWin.prototype.disableInput = function () {
    this.input
      .attr('disabled', true)
      .val(this.options.busy_text);
  }

  /**
   * Reenable input after running disableInput()
   */
  TerminalWin.prototype.enableInput = function () {
    this.input
      .removeAttr('disabled')
      .val(this.autofill);
  }

  TerminalWin.prototype.showEditor = function (content, saveCall, quitCall) {
    
    if (this.options.source == 'contentjs') {
      console.log('terminal new -c `'+this.command.input+'`')
        this.handleInput('terminal new -c `'+this.command.input+'`')
        return
    }

    window.EditorWin.showEditor(content, saveCall, quitCall);
  }

  return TerminalWin;
}));
